Call the platform from the zone

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

ts
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:

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

ts
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.

ts
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

ts
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:

ts
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.