Skip to main content
An agency integration lets one operator run merch for many creators from a single console. One confidential channel.* credential operates a whole fleet of subaccount shops: you onboard creators, browse and publish to each shop, and surface live storefronts — all from one secret. The worked example for this guide is Greenroom, an agency console you can read end to end.
The Channel API requires special access — it is invite only and currently in beta. Your channel must be granted access by Fourthwall, which provisions a dedicated agency-bound channel.* client for it; reach out to request access. See Authentication and Requesting an access token.

One secret, three faces

The single agency secret authenticates three different ways depending on the call. The secret never reaches the browser — every channel-api and open-api call goes through a server route that mints the token and attaches it.
FaceAuthenticates withOperates on
channel-apibearer token alonethe agency channel + its bound shop
open-apibearer token + X-ShopId headerone ownership-checked subaccount
publicTokena browser-safe storefront tokenthe live shop, read-only
X-ShopId is ownership-checked upstream: a missing or unowned shop id is rejected, so you can only ever act on a shop your agency manages.

The token mint — HTTP Basic, not body credentials

The agency uses the client credentials grant, sending the id/secret as client_secret_basic (HTTP Basic). Posting the secret in the request body returns 401 invalid_client — the single most common first mistake:
const basic = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64");
await fetch(TOKEN_URL, {
  method: "POST",
  headers: {
    Authorization: `Basic ${basic}`,
    "Content-Type": "application/x-www-form-urlencoded",
  },
  body: new URLSearchParams({ grant_type: "client_credentials" }),
});
Cache the token until shortly before it expires, then mint a fresh one.

Two clients over one token

  • channelApi — attaches Authorization: Bearer <token> only.
  • openApi — attaches the bearer and the X-ShopId header for the target subaccount.

Five features, mapped to the three faces

1

F1 — Fleet home (channel-api)

Identify the connected agency and list its fleet of shops.GET /channel-api/v1.0/channel/current + GET /channel-api/v1.0/shops
2

F2 — Onboard a creator (channel-api)

Create a subaccount shop, invite the owner, and optionally start payout onboarding. The creation response carries the new shop’s publicToken.POST /channel-api/v1.0/shops
3

F3 — Subaccount catalog (open-api)

Browse one shop’s products, scoped by X-ShopId.GET /open-api/v1.0/products with X-ShopId: <subaccount>
4

F4 — Design & publish (two faces)

The load-bearing flow — it touches both faces. See the next section.
5

F5 — Creator storefront (publicToken)

Resolve the shop’s browser-safe publicToken server-side, then read the live shop directly from the browser against the Storefront API — no agency secret in play.PUT /open-api/v1.0/public-token (server) → Storefront API (browser)

F4 — why artwork is registered twice

F4 renders an instant preview on the channel’s bound shop, then publishes on the subaccount. The artwork is registered twice, across two faces: The bound-shop imageId from the preview is not valid for the design-create on the subaccount — that resolves the imageId against the X-ShopId shop, so the publish path must register the media again on the subaccount.
Gotchas the example pins down:
  • The region is template-specific. The renderer rejects an invalid region and returns the valid ones — front for DTG, front_dtf for DTFX, etc. Derive the region from the template’s productionMethod.
  • colors / sizes are case-sensitive against the product’s variant labels, or the render comes back empty. Leave them empty to use all available.
  • publishOnCreate controls whether the product is created PUBLIC or HIDDEN. When off, transition it to PUBLIC via PUT /products/{id}/state after creation.
  • The presigned upload PUT must echo two signed headersContent-Type and x-goog-content-length-range: 0,<size> (the byte length declared in the upload-url request) — or GCS returns 403 SignatureDoesNotMatch.

F5 — the publicToken face

The publicToken is browser-safe. Resolve it server-side (GET /api/shops/{shopId}/public-tokenPUT /open-api/v1.0/public-token) and then read the live shop directly from the browser against the Storefront API — no agency secret in play. The token comes from F2’s creation response (for shops created in-session) or the public-token endpoint (for any managed shop).
Onboarding creates real shops and real payouts. Target staging by default while building, and treat a payout account as pending after the handoff — returning from the returnUrl means the creator left the hosted flow, not that payouts are verified.

Full example

Read Greenroom end to end — a Next.js console where lib/fourthwall.ts holds the credential core (token mint + the two clients) and every Fourthwall call runs through a server route.