Registering a zone is the one onboarding step with no admin UI. It is a code change in the platform's own source plus a redeploy, so unless you maintain the platform, this is a request you make rather than a step you perform.
Everything else about an agent can be configured by a platform admin in the browser. This cannot, because the registry is read at build time to generate the asset proxy.
To make the request, send the maintainer the four values below — they are everything needed, and supplying them together avoids a round trip:
| Example | Notes | |
|---|---|---|
| Org slug | acme | Must already exist |
| Agent slug | revenue-chat | Must match the agent record exactly |
| Audience | cxpa-revenue-chat | You choose it; convention is cxpa-<agent-slug> |
| Zone URL | https://acme-revenue-chat.vercel.app | Deploy first, no trailing slash |
Ask them to confirm two things when it is done: that the platform was redeployed without build cache, and whether the agent record already existed — that determines whether you get a zone JWT secret or have to rotate for one (step 4).
Nothing in this page blocks local development. See Local development modes — two of the three need no platform at all.
1. Add the registry entry
The platform's zone registry maps org slug → agent slug → zone config. It lives in the platform's source, not in a UI surface, so this step goes through whoever maintains the platform:
acme: {
'revenue-chat': {
audience: 'cxpa-revenue-chat',
url: process.env.ZONE_REVENUE_CHAT_URL,
},
},
audience is the JWT audience claim the platform signs with and the zone verifies against. It must equal the zone's AUDIENCE_ID exactly. It is not derived from anything — pick a stable string and use it in both places. The convention is cxpa-<agent-slug>.
url is the zone's origin, read from an environment variable so it can differ per platform environment. A falsy value disables the zone: the platform stops proxying and the agent falls back to the built-in JSON editor. That is the intended way to turn a zone off.
The keys must match the org and agent slugs exactly — they are what the incoming request path is matched against.
2. Set the URL variable
ZONE_REVENUE_CHAT_URL=https://acme-revenue-chat.vercel.app
Locally that points at your zone dev server instead:
ZONE_REVENUE_CHAT_URL=http://localhost:4000
No trailing slash. A stray / produces a doubled path in the proxy destination and a redirect loop whose response body is unhelpful text/plain.
The variable name is not derived from the slug — it is written by hand in the registry entry — so choose something readable and keep it consistent across environments.
3. Redeploy the platform without build cache
The platform generates one asset rewrite per registered zone, mapping /assets/{orgSlug}/{agentSlug}/_next/* to the zone. Those rewrites are computed at build time. Adding the URL variable without a rebuild means the page proxies but every stylesheet and script 404s — a zone that renders as unstyled HTML.
On Vercel: Deployments → … → Redeploy, and uncheck "Use existing Build Cache".
4. Hand the secret to the zone
If the agent record was created after the registry entry existed, its zone JWT secret already exists. Read it at /acme/revenue-chat/zone-jwt-secret and set it in the zone as ZONE_JWT_SECRET.
If the page says Not set — rotate to generate, the registry entry came later. Press Rotate Secret; that generates it. This is the recovery path mentioned in Create the agent, and it is harmless — nothing else depends on the secret yet.
Rotating later is also fine, but it is a two-step change: rotate on the platform, then redeploy the zone with the new value. Between those two steps every request to the zone 401s.
5. The complete zone environment
| Variable | Value | Needed at |
|---|---|---|
ZONE_ORG_SLUG | acme — must match the registry key | Build time |
ZONE_AGENT_SLUG | revenue-chat — must match the registry key | Build time |
AUDIENCE_ID | cxpa-revenue-chat — must match the registry audience | Request time |
ZONE_JWT_SECRET | From /acme/revenue-chat/zone-jwt-secret | Request time |
PLATFORM_ORIGIN | The platform's origin, no trailing slash | Build time |
PLATFORM_API_BASE_URL | The platform's origin, no trailing slash | Request time |
The first two are compiled into the build, so changing them requires a rebuild, not just a restart.
Each of the first four has a distinct failure mode, which makes debugging easier than it looks:
| Wrong value | Symptom |
|---|---|
ZONE_ORG_SLUG / ZONE_AGENT_SLUG | Builds cleanly, then every request 404s |
AUDIENCE_ID | 401 on every request — signature valid, audience rejected |
ZONE_JWT_SECRET | 401 on every request — signature invalid |
PLATFORM_ORIGIN | Pages render, server actions fail as cross-origin |
6. Order of operations
The dependency chain is real, and doing these out of order is the most common way a first zone deployment goes wrong:
- Deploy the zone and note its production URL. It will 401 everything until the platform is wired up — that is correct, not a failure.
- Add the registry entry and set
ZONE_REVENUE_CHAT_URLon the platform. - Redeploy the platform without build cache.
- Create the agent — this is when the zone JWT secret is minted. (If the agent already existed, rotate the secret instead.)
- Set
ZONE_JWT_SECRETandAUDIENCE_IDon the zone and redeploy it.
Steps 2 and 4 are the pair that matters: the registry entry must exist before the agent record is created, or step 4 mints nothing.
7. Local development modes
There are three, and only the third needs the platform. If you do not have the platform running locally — which most zone authors will not — the first two are your loop.
Mode 1 — bypass the token (no platform)
Set DISABLE_ZONE_AUTH=true and DEV_AGENT_ID in web/.env.local, run the zone on its own port, and browse it directly at http://localhost:4000/acme/revenue-chat/app. The middleware waves every request through and getZoneSession() returns a fabricated session. This is the normal day-to-day loop: it exercises your pages, server actions and database code, and needs nothing else running.
What it does not exercise is authentication — see the warning in Build the zone app.
Mode 2 — prove the middleware fires (no platform)
With DISABLE_ZONE_AUTH unset, run the zone and check it rejects unauthenticated traffic:
curl -i http://localhost:4000/acme/revenue-chat/app
# → 401 (no zone token)
curl -i -H "x-zone-token: garbage" http://localhost:4000/acme/revenue-chat/app
# → 401 (invalid signature)
curl -i http://localhost:4000/
# → 404 (outside the base path — the matcher only fires inside it)
Run this before every deployment. It takes seconds and it is what catches a middleware matcher that silently matches nothing — the failure that leaves a zone publicly readable without any error to notice.
Mode 3 — end to end through the platform (needs the platform)
Only possible if you can run the platform locally. Start both servers, with the platform's ZONE_REVENUE_CHAT_URL pointing at your zone:
# terminal 1 — platform
npm run dev
# terminal 2 — zone
npm run dev -- -p 4000
Visit http://localhost:3000/acme/revenue-chat/app. The address bar stays on port 3000, the page body comes from port 4000, and assets are proxied. This is the only way to see a real minted JWT locally.
If you cannot run the platform, you are not blocked: modes 1 and 2 cover the code and the auth boundary respectively, and the first real token your zone sees will be on a deployed environment. Deploy to a preview and check it there.
8. Verify
GET https://agents.collectivexp.ai/acme/revenue-chat/apprenders your zone.- In the network tab,
_next/*requests come from the platform's origin, not the zone's. If they come from the zone's origin,assetPrefixis a URL instead of a path. curl -i https://your-zone.vercel.app/acme/revenue-chat/appreturns 401. If it returns 200, the middleware matcher is wrong and the zone is publicly readable.- The agent's sidebar shows an App link, which opens the zone in a new tab.
That third check is worth running on every deployment. It is the one that catches a zone that fails open.
Next
Run lifecycle and progress — showing the user what is happening.