createZoneClient returns a client that authenticates every request with the bearer token from the verified session. It exposes two namespaces: runs and credentials.
Construct the client
import { createZoneClient, getSession } from '@cxpa/sdk/zone'
const session = await getSession()
const cxpa = createZoneClient({
baseUrl: process.env.PLATFORM_API_BASE_URL!,
token: session.token,
agentId: session.claims.agentId,
})
Server-action pattern
The browser must not see the bearer token. Route every platform call through a 'use server' function. By convention, return { error: string } on failure rather than throwing — the page renders an inline error surface instead of a Next.js error boundary.
src/lib/actions.ts:
'use server'
import { createZoneClient, getSession, type Run } from '@cxpa/sdk/zone'
async function client() {
const session = await getSession()
return createZoneClient({
baseUrl: process.env.PLATFORM_API_BASE_URL!,
token: session.token,
agentId: session.claims.agentId,
})
}
export async function triggerRun(
input: { url: string },
): Promise<{ runId: number } | { error: string }> {
try {
const cxpa = await client()
const { runId } = await cxpa.runs.create({ payload: input })
return { runId }
} catch (err) {
return { error: err instanceof Error ? err.message : 'Unknown error' }
}
}
export async function pollRun(
runId: number,
): Promise<{ run: Run } | { error: string }> {
try {
const cxpa = await client()
return { run: await cxpa.runs.get(runId) }
} catch (err) {
return { error: err instanceof Error ? err.message : 'Unknown error' }
}
}
'use server' constraint: this file can only export async functions. No types, no constants, no re-exports.
Read a run
const run = await cxpa.runs.get(runId)
// run.status, run.output_data, run.error, …
For live status, poll runs.get or use the platform's Realtime channel (out of scope for the zone SDK in this release).
Trigger a run and wait for its output
When the zone UI needs the agent's output in the same response — a "Generate Report" button that returns a PDF link, an "Analyze" button that renders the AI summary inline — use runs.createAndWait instead of runs.create. The call blocks until the run reaches a terminal state or the server-side wait window elapses.
export async function generateReport(
topic: string
): Promise<{ pdfUrl: string } | { error: string } | { pending: true; runId: number }> {
try {
const cxpa = await client()
const result = await cxpa.runs.createAndWait({
payload: { topic },
timeoutMs: 120_000,
})
if (result.timedOut) {
// The run is still in flight — fall back to polling with cxpa.runs.get.
return { pending: true, runId: result.runId }
}
if (result.status === 'completed') {
return { pdfUrl: result.output_data?.pdfUrl as string }
}
return { error: result.error ?? `Run ${result.status}` }
} catch (err) {
return { error: err instanceof Error ? err.message : 'Unknown error' }
}
}
The effective wait is capped by the agent's running_timeout_ms and a platform hard ceiling. Pick createAndWait for short, output-shaped agents; pick create + polling for long runs or when the UI can render progress separately.
Resolve a credential
const hubspot = await cxpa.credentials.get('hubspot')
if (hubspot.type === 'oauth') {
await fetch('https://api.hubapi.com/...', {
headers: { authorization: `Bearer ${hubspot.access_token}` },
})
}
The integrationKey is the integration's slug as configured on the agent. Returns one of three discriminated shapes — see Resolve a credential.
Errors
All methods throw ApiError on non-2xx responses:
import { ApiError } from '@cxpa/sdk/zone'
try {
await cxpa.runs.create({ payload: input })
} catch (err) {
if (err instanceof ApiError) {
console.error(err.status, err.message)
}
throw err
}
Continue
When you are ready, deploy and register the zone.