@ccen/app-bridge

The typed contract every CCEN app uses.

One SDK. One surface across web, the agent runtime, and embedded shells. Read live data, mutate with audit trail, open host modals, register shortcuts, theme tokens, locale. iframes do the isolation. The SDK does the typing.

# Install
npm install @ccen/app-bridge

# Or with the CLI scaffold
npx ccen create my-returns-app --template returns
SDK surface@ccen/app-bridge
ccen.orders
listgetupdatetagpaginate
ccen.products
listgetcreateupdatemetafields
ccen.customers
listgetsegmentrfm.bucket
ccen.modal
opencloseconfirm
ccen.shortcut
registerunregister
ccen.theme
tokensonChangemode
ccen.tools
registerlistinvoke
ccen.context
merchantIduserIdlocaletz
12 namespaces · 84 methods · fully typed · zero any.
Anatomy

One config. Two boundaries.

Every CCEN app is defined in one TypeScript module. The SDK draws the line at the iframe boundary. You write business logic. We move the bytes.

// apps/returns-pro/src/index.ts
import { defineApp } from "@ccen/app-bridge";
import { z } from "zod";

export default defineApp({
  id: "returns-pro",
  name: "Returns Pro",
  scopes: ["orders:read", "returns:read", "returns:write"],

  routes: {
    "/": HomeView,
    "/rmas/:id": RmaDetail,
  },

  shortcuts: {
    "cmd+r": ({ navigate }) => navigate("/rmas/new"),
  },

  tools: {
    "issue-refund": {
      input: z.object({
        orderId: z.string(),
        amount: z.number().positive(),
        reason: z.enum(["damaged", "wrong_item", "changed_mind"]),
      }),
      requires: "approval",
      handler: async ({ orderId, amount, reason }, { ccen }) => {
        return ccen.refunds.create({ orderId, amount, reason });
      },
    },
  },
});
Scopes

Declared up front. The host enforces them at the API boundary. Operators see exactly what your app can read and write at install time.

Routes

Each route is a React component. The host renders your iframe at the right URL. Deep links work. Browser back works.

Shortcuts

Scoped to your app. They fire when focus is inside your iframe. They never collide with host shortcuts.

Tools

The agent runtime calls these. Every tool is typed (Zod). Approval policy lives next to the handler.

Surfaces

Six namespaces. Most apps need three.

Every method is typed. Every payload is validated at the host boundary. The SDK is small (the bundle is a single iife script per app).

ccen.orders / .products / .customers / .listings

L0 entity access

Read and write every L0 entity through typed methods. Filters compile to indexed queries. All mutations record actor, source app, and scope to the audit log.

const orders = await ccen.orders.list({
  status: "processing",
  channel: ["shopify", "amazon"],
  limit: 200,
});

await ccen.orders.tag(order.id, ["fraud-watch"]);
ccen.modal

Host modals

Open a modal that renders in the host frame so it escapes your iframe. Pass JSX or markdown. The host serializes and validates the payload before render.

await ccen.modal.open({
  title: "Confirm refund",
  body: <RefundReview order={order} />,
  cta: { label: "Refund $84.20", variant: "primary" },
  cancel: { label: "Cancel" },
  size: "md",
});
ccen.shortcut

Keyboard shortcuts

Register shortcuts scoped to your app. They fire only when focus is inside your iframe. The host owns Cmd+K and a small reserved set.

ccen.shortcut.register("cmd+s", saveDraft);
ccen.shortcut.register("g r", () => navigate("/rmas"));

// Unregister on unmount
return () => ccen.shortcut.unregister("cmd+s");
ccen.theme

Theme tokens

Receive the full token set on mount. Subscribe to changes for light, dark, or merchant-custom themes. The SDK injects CSS variables into your iframe automatically.

const tokens = await ccen.theme.tokens();
ccen.theme.onChange((next) => applyTokens(next));

// Tokens include radius, color, spacing, type
console.log(tokens.color.primary);
console.log(tokens.radius.md);
ccen.notify

Notifications

Send transactional and operational notifications through the host. Routing, priority, and channel selection are handled by Knock under the hood.

await ccen.notify({
  to: order.assignee,
  template: "fraud-flag",
  data: { orderId: order.id },
  priority: "high",
});
ccen.tools

Agent tools

Register tools your app exposes to the agent runtime. Each tool is typed with Zod and declares its approval policy. Tools execute under the agent identity in the audit log.

ccen.tools.register({
  name: "rebalance-inventory",
  input: z.object({
    sku: z.string(),
    fromLocation: z.string(),
    toLocation: z.string(),
    quantity: z.number().int().positive(),
  }),
  description: "Move inventory between warehouses",
  requires: "approval",
  handler: async (input) => ccen.transfers.create(input),
});
Scopes and auth

Operators see exactly what you ask for.

Every scope your app declares shows up in the install dialog. There is no hidden surface. Tokens are short-lived and scoped to the install, not the developer account. RLS at the API boundary enforces them.

Per-install tokens

Tokens are scoped to (app, merchant, install). Rotated automatically. Revoked instantly when the operator uninstalls.

RLS-backed scopes

Postgres row-level security backs every scope. We do not trust the SDK alone. The API boundary re-checks every call.

Per-app subdomain

Apps run from <app-id>.apps.ccen.co. Cookies, storage, and CSP are scoped per origin. No shared JS context with the host.

Scope catalog12 of 24 shown
orders:readList and get orders, line items, fulfillments.
orders:writeUpdate tags, holds, fulfillments, refunds.
products:readList products, variants, inventory levels.
products:writeCreate products, manage variants and metafields.
customers:readList, get, and segment customers.
customers:writeUpdate customer records and tags.
listings:readRead per-channel listings and content.
listings:writeUpdate titles, descriptions, images, prices.
agents:toolsRegister tools the agent runtime can invoke.
files:readRead merchant files, packing slips, design assets.
files:writeUpload merchant files (e.g. labels, slips).
webhooks:writeSubscribe and unsubscribe to host events.
Need a scope that does not exist? platform@ccen.co. We’ll add it within a release.
Dev loop

Local-first. Real data on tap.

The CLI runs a real CCEN host on localhost. Your app loads inside it. Read scopes return mocked data by default; write scopes go to a sandbox merchant. Connect a dev install to read real data when you need to.

The dev host hot-reloads on file change. Theme tokens, locale, and timezone are configurable from a debug drawer.

01
$ npx ccen create returns-pro --template returns

Scaffold an app from a template. Three apps come with the CLI.

02
$ npx ccen dev

Run a sandboxed CCEN host on localhost. Hot-reload your iframe.

03
$ npx ccen test

Run typed contract tests against a mocked SDK. CI-friendly.

04
$ npx ccen publish --version 0.4.0

Submit to the marketplace, or publish privately to your org.

Versioning

Stable surfaces. Honest deprecations.

The SDK follows semver. Major versions are a year apart. Stable surfaces are guaranteed for two majors. Preview surfaces are flagged inline in the types and the docs.

v3.xCurrent stable. Released March 2026.stable
v2.xSupported through November 2026.supported
v1.xDeprecated. Read-only methods still respond.deprecated
v4.x previewUnder flag. Schedule support guarantees.preview
SDK FAQ

Things developers ask.

Why iframes instead of a JS plugin model?
Real isolation. Apps cannot read each other's state, cannot break the host, cannot leak secrets across origins. Browser-enforced security with Stripe and Shopify track records. We rejected Shadow DOM (style-only encapsulation), Web Workers (no DOM), and WASM UI sandboxes (still renders through host JS).
Can my app run outside CCEN?
Yes. The SDK has a `local` mode for development and the host shell is open source. You can host the iframe contract in your own admin if it makes sense. Most developers don't, because the marketplace is where the operators are.
Are there rate limits?
Yes. Per-install rate limits on read and write scopes. Tool calls have separate limits with budget enforced per app. Limits scale with the operator tier. Documented per surface in the reference.
How do I handle errors?
Every method returns typed Result objects with discriminated error codes. The SDK never throws on operational errors. It does throw on programmer errors (wrong type, missing scope) so they surface in dev.
Does the SDK work with Server Components?
Yes. SDK calls work in both client and server components. Reads inside server components use the operator's session token. Writes are always client-initiated and audit-trailed.
Is there a TypeScript-only requirement?
No. The SDK ships type definitions but vanilla JS works. We strongly recommend TypeScript because the value of typed scopes and inputs is real.

Read the reference. Then ship.