SDK & API

Links and authentication API

Typed navigation, auth and intent APIs with account revalidation and public install-identity boundaries.

Alpha documentation0.6.0-alpha.1

Contract and use

Navigation, sign-in and action handoff are different capabilities. A tab request is not a loaded page; a completed browser flow is not an authenticated backend session; an intent receipt is not independent proof of delivery. These excerpts preserve current SDK declarations, including optional methods and target differences.

Request navigation without claiming a load

Use client.capability('links') or await client.require('links'); there is no client.links getter in this SDK. Package IDs such as uk.capp.sidecar.links belong to selection, not capability discovery.

ts
import type { CappClient } from '@capp/app-sdk';

export async function requestDeveloperHelp(client: CappClient): Promise<string> {
  const links = await client.require('links', 5000);
  if (typeof links.open !== 'function') throw new Error('In-app navigation is unavailable');
  await Promise.resolve(links.open('https://c-app.uk/developer-docs/', { mode: 'tab' }));
  return 'Help tab requested';
}

Invoke this from a button and guard against duplicate pending clicks. A failure may be uncertain; offer a separate ordinary HTTPS link rather than automatically launching another destination. The selected policy can ask, use tabs, require the default browser or disable handling. mode: 'tab' is a request, not authority to override that policy.

Saved links belong to this app on this device. Inspect status().storage: session does not promise persistence, and damaged/unavailable must not be presented as a successful saved collection. Export can include private browsing destinations; review before sharing. Hiding a view does not delete bookmarks or close other features' tabs.

Complete sign-in, then re-read your account

auth.open, start, current, lease, user and status vary by runtime. A status() parameter may be a flow ticket; do not assume it always means “who is signed in?” Prefer the Android Auth guide for the selected build mode.

This application-owned helper uses open() and then your existing authenticated account endpoint. It does not infer identity from an arbitrary flow result or copy credentials into JavaScript.

ts
import type { CappClient } from '@capp/app-sdk';

export async function signInAndReadAccount<T>(
  client: CappClient,
  readAuthenticatedAccount: () => Promise<T>,
): Promise<T> {
  const auth = await client.require('auth', 5000);
  if (typeof auth.open !== 'function') throw new Error('This runtime has no supported sign-in flow');
  await auth.open({ embed: true, surface: 'sidecar' });
  return await readAuthenticatedAccount();
}

The callback must reject when its server reports an unauthenticated user; its name is not validation. Keep one sign-in flight at a time. Closing/cancelling a flow is not approval to keep polling forever. A public authenticated indicator is suitable for UI state, not as backend authorization for a write or purchase.

Installation trust and logout

Use await client.security.installIdentity() when an authenticated enrolment flow needs public key material. Verify the result protocol and identity fields; do not supply another product's appId. That public key is not a user session, and possession of an enrolment payload alone must not let a backend accept an arbitrary installation.

The host retains reusable credentials and the private signer. A local clear/logout does not necessarily revoke a remote session, subscription, wallet authority or another device's installation. Your product's logout workflow must define which local and remote state it actually clears. Preserve accurate UI wording when a remote revocation is unconfirmed.

Intent handoff is a separate contract

Use client.intent for supported app-declared handoffs. Keep the original scope/request identity with any returned receipt. An empty object or merely returned promise is not a verified result. current(scope) and receipts() inspect retained outcomes; clear() removes local records, not an external action. Do not retry a purchase or valuable mutation just because the hosted view closed without a reply.

Troubleshooting

SymptomNext check
client.links is missingUse the capability lookup, not a nonexistent convenience getter.
Navigation requested but content blankInspect target support, selected link policy and destination embedding restrictions.
Sign-in flow returns but protected request is 401Refresh account through the existing transport/session path; do not fabricate a bearer.
Public identity exists but gateway rejects itCheck authenticated enrolment, expected app/site/binding, revocation and server trust storage.
Logout clears UI but another session worksLocal clearing and remote revocation have different scope.
Newer runtime method absent from these typesCheck the exact installed component version; do not advertise it as portable SDK support.
ts
export type CappSavedLink = {
    id: string;
    url: string;
    title: string;
    folder: string;
    notes: string;
    pinned: boolean;
};

CappLinksTab

ts
export type CappLinksTab = {
    id: string;
    title?: string;
    kind?: 'app' | 'blank' | 'file' | 'web';
    url: string | null;
    active: boolean;
    canBack: boolean;
    canForward: boolean;
};

CappLinksStatus

ts
export type CappLinksStatus = {
    layout?: 'window-tabs';
    activeTab?: string;
    nativeBrowsing?: boolean;
    linkHandling: 'ask' | 'tab' | 'browser' | 'off';
    tabCount?: number;
    version: string;
    count: number;
    limit: number;
    storage: 'ready' | 'session' | 'unavailable' | 'damaged';
    error: string | null;
    open?: boolean;
    pageRequested?: boolean;
};

CappLinksBridge

ts
export type CappLinksBridge = {
    version: '1';
    status(): CappLinksStatus;
    list(options?: {
        query?: string;
        folder?: string | null;
    }): CappSavedLink[];
    save(input: {
        url: string;
    } & Partial<CappSavedLink>): CappSavedLink;
    remove(id: string): {
        removed: true;
    };
    import(json: string): {
        added: number;
        skipped: number;
    };
    export(): string;
    refresh(): CappLinksStatus;
    show(): {
        open: boolean;
    };
    hide(): {
        open: boolean;
    };
    view(url: string): {
        requested: boolean;
        mode: 'embedded' | 'browser-required' | 'native' | 'file';
    };
    openExternal(url: string): Promise<unknown>;
    open(url: string, options?: {
        mode?: 'ask' | 'tab' | 'browser';
    }): unknown;
    newTab(url?: string | null): {
        tabId: string;
    };
    tabs(): CappLinksTab[];
    selectTab(id: string): {
        tabId: string;
    };
    closeTab(id: string): {
        closed: true;
    };
};

CappAuthBridge

ts
export type CappAuthBridge = {
    open?: (payload?: Record<string, unknown>) => Promise<CappNativeAuthState | Record<string, unknown> | null>;
    start?: (payload?: Record<string, unknown>) => Promise<unknown>;
    status?: (ticketOrPayload?: unknown) => Promise<unknown>;
    current?: () => CappNativeAuthState | Record<string, unknown> | null | Promise<CappNativeAuthState | Record<string, unknown> | null>;
    lease?: () => CappNativeAuthState | Record<string, unknown> | null | Promise<CappNativeAuthState | Record<string, unknown> | null>;
    clear?: () => unknown;
    logout?: () => Promise<unknown>;
    user?: () => Promise<unknown>;
};

CappIntentBridge

ts
export type CappIntentBridge = {
    open?: (payload?: Record<string, unknown>) => Promise<unknown>;
    start?: (payload?: Record<string, unknown>) => Promise<unknown>;
    status?: (ticketOrPayload?: unknown) => Promise<unknown>;
    current?: (key?: string) => unknown;
    receipts?: () => Record<string, unknown>;
    clear?: () => unknown;
};

CappNativeAuthState

ts
export type CappNativeAuthState = {
    protocol: 'capp.native-auth-state.v1' | string;
    ok?: boolean;
    authenticated: boolean;
    lease?: Record<string, unknown> | null;
    user?: Record<string, unknown> | null;
    storageProtection?: string | null;
    hardwareBacked?: boolean | null;
    [key: string]: unknown;
};

CappInstallIdentity

ts
export type CappInstallIdentity = {
    protocol: 'capp.install-identity.v1' | string;
    installId: string;
    keyId: string;
    algorithm: 'Ed25519' | string;
    appId?: string | null;
    siteId?: string | null;
    createdAt?: string | null;
    storageProtection?: string | null;
    hardwareBacked?: boolean | null;
    publicKeyJwk: {
        kty: 'OKP' | string;
        crv: 'Ed25519' | string;
        x: string;
        kid?: string;
        [key: string]: unknown;
    };
    [key: string]: unknown;
};

SDK overview · Build with Sidecars · Security and authority

↑ ↓ to navigate · Enter to open · Esc to close. Search stays in your browser.