@adcp/sdk API Reference - v14.3.0
    Preparing search index...

    Function createDerivedAccountStore

    • Build an AccountStore<TCtxMeta> for an agent fronting an upstream-managed account namespace.

      The factory:

      1. Sets resolution: 'derived'.
      2. Throws AdcpError('AUTH_REQUIRED') when ctx.authInfo carries no credential (skip with skipAuthCheck: true). The check accepts the discriminated credential shape (preferred) AND the deprecated token / clientId fields populated by pre-#1269 authenticators — fail-closed only when none of the three are present.
      3. Verifies buyer-supplied account_id against what the credential can reach, and returns null on any miss so the framework emits the spec's fixed ACCOUNT_NOT_FOUND envelope. Verification is performed by the factory, not delegated to adopter code — an adopter cannot forget it and silently serve cross-tenant requests.
      4. Auto-selects the singleton for ref-less calls (list_creative_formats, provide_performance_feedback, …) when the credential reaches exactly one account; returns null when it reaches several, because "whichever one" is not a defensible default.
      5. Wires list (list_accounts) — required for 'derived' platforms, and the only way buyers learn ids. Honors the wire filters the framework passes through (account, status, sandbox) and pages the result (pagination.max_results, capped at 100, with an opaque cursor).
      6. Omits upsert. Natural-key provisioning is out of scope for this mode (the framework fails those sync_accounts entries per-row with UNSUPPORTED_PROVISIONING); adopters whose upstream supports settings-update writes compose upsert on top via spread.

      Refuses the brand+operator arm. The framework rejects { brand, operator } references for 'derived' platforms with AdcpError('INVALID_REQUEST', { field: 'account.brand' }) before reaching this resolver, pointing the buyer at list_accounts. The factory also returns null for such refs defensively.

      Type Parameters

      • TCtxMeta = Record<string, unknown>

      Parameters

      Returns AccountStore<TCtxMeta>

      import { createDerivedAccountStore } from '@adcp/sdk/server';

      const accounts = createDerivedAccountStore<AudioStackAccountMeta>({
      toAccount: async (ctx) => {
      const workspace = await audiostack.currentWorkspace(ctx?.authInfo);
      return {
      id: workspace.id, // the id buyers will send back
      name: workspace.name,
      status: 'active',
      ctx_metadata: {}, // tokens stay on ctx.authInfo, not here
      };
      },
      });
      const accounts = createDerivedAccountStore<{ upstreamId: string }>({
      listAccounts: async (ctx) => {
      const rows = await meta.adAccountsFor(ctx?.authInfo); // credential-scoped
      return rows.map(r => ({
      id: r.account_id,
      name: r.name,
      status: r.disabled ? 'suspended' : 'active',
      ctx_metadata: { upstreamId: r.id },
      }));
      },
      });
      const accounts = createDerivedAccountStore({
      listAccounts: (ctx) => upstream.page(ctx?.authInfo),
      // MUST filter by the caller, not just by id:
      lookupAccount: (id, ctx) => upstream.accountForCaller(id, ctx?.authInfo),
      });
      const accounts: AccountStore<MyMeta> = {
      ...createDerivedAccountStore({ listAccounts }),
      // Entries reaching this are `account: { account_id }`-keyed AND already
      // resolved against the caller's reachable set — the framework refuses
      // natural-key provisioning and unreachable ids for 'derived'. Returned
      // rows must still carry `brand` + `operator` (schema-required); echo
      // them from your own account record.
      upsert: async (refs, ctx) => myUpstream.updateSettings(refs, ctx),
      };