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
channel.* client for it;
reach out to request access. See
Authentication and
Requesting an access token.The defining idea: the shop appears on publish
Browsing templates, uploading artwork, and rendering a preview are all shop-less — they go straight through the Channel API on the credential alone. Only on publish does a shop come into play: your backend provisions one (once) and creates the live product against it.Preview is shop-less; create is shop-bound.
| Wizard step | What happens | API call | Shop? |
|---|---|---|---|
| 1 · Pick a product | Browse blank product templates | GET /open-api/v1.0/product-templates/page/{page} | No |
| 2 · Add artwork | Presigned upload, then register the image | POST /channel-api/v1.0/upload-url → POST /channel-api/v1.0/media/images | No |
| 3 · Preview | Render preview images synchronously | POST /channel-api/v1.0/previews | No |
| 4 · Details & price → Publish | Provision a shop on first publish, then create the product | GET /channel-api/v1.0/shops → (first time) POST /channel-api/v1.0/shops → POST /open-api/v1.0/products | Creates / needs it |
Two credential faces, one channel
The same channel credential authenticates two API surfaces:- channel-api (
/channel-api/v1.0/…) — authorized by the channel bearer alone. Everything shop-less: template browsing, upload, preview, shop lookup, and shop provisioning. - open-api (
/open-api/v1.0/…) — the product-templates list is shop-less, but product create / list / state / delete are shop-bound: the same bearer plus anX-ShopIdheader selects the shop you provisioned.
Architecture: a thin backend-for-frontend
Keep the credential on your server. Each/api/* route is a small proxy that
attaches the channel credential and forwards to one (or, for publish, a few)
Fourthwall endpoints. The browser only ever talks to your own routes.
| Your route | Forwards to | Shop? |
|---|---|---|
GET /api/templates | GET /open-api/v1.0/product-templates/page/1 | No |
GET /api/channel | GET /channel-api/v1.0/channel/current | No |
POST /api/upload-url | POST /channel-api/v1.0/upload-url | No |
POST /api/media | POST /channel-api/v1.0/media/images | No |
POST /api/previews | POST /channel-api/v1.0/previews | No |
POST /api/publish | shop lookup → create (first only) → POST /open-api/v1.0/products | Needs one |
GET /api/links | GET /channel-api/v1.0/shops → GET /open-api/v1.0/products | Needs one |
PUT /api/links/{id}/visibility | PUT /open-api/v1.0/products/{id}/state | Needs one |
DELETE /api/links/{id} | DELETE /open-api/v1.0/products/{id} | Needs one |
The flow, step by step
Connect the channel
Install your channel app and exchange the OAuth code for the channel
credential, holding it server-side. Confirm the connection by reading the
current channel:
cURL
Pick a blank product
List blank product templates and let the creator choose one. This is
shop-less:
cURL
Upload and register the artwork
Request a presigned upload URL,
PUT the bytes, then register the image to
get an imageId. Both calls are on the channel face — no shop yet:cURL
Render a live preview
Render preview mockups synchronously from the chosen template +
imageId.
Still shop-less:cURL
Publish — the shop boundary
Publish is the only step that needs a shop, and it owns the whole shop
lifecycle so your client makes a single call:
GET /channel-api/v1.0/shops— does the channel have a shop yet?POST /channel-api/v1.0/shops— only on first publish (idempotent: a second publish reuses the same shop, never a duplicate).POST /open-api/v1.0/productswith theX-ShopIdheader — create the live product from the sameproductId+imageIdthe preview was rendered from.
cURL
Managing published products
Once a shop exists, manage the catalog on the open-api face withX-ShopId:
- Show / hide →
PUT /open-api/v1.0/products/{id}/state(PUBLIC/HIDDEN) - Archive →
DELETE /open-api/v1.0/products/{id} - List →
GET /open-api/v1.0/products
Full example
Read Linkstand end to end — a Next.js app-router project where the BFF inlib/fourthwall.ts
is the only module that holds the credential and talks to Fourthwall.