# SDK reference

> @playcode/sdk at a glance: its entry points, the client's options, every option of a call, an agent and a run, the events, the errors and the defaults.
> Source: https://playcode.io/docs/developers/sdk-reference - last reviewed 2026-10-17.

Every part of `@playcode/sdk` in tables. The package's types describe each option too, so your editor shows them as you type.

## Entry points

| Import | Holds | Where it runs |
|---|---|---|
| `@playcode/sdk` | `Playcode`, `tool`, `memoryStore`, `memoryEventBus`, `verifyWebhook`, the errors and every type | Your server |
| `@playcode/sdk/ai` | `playcode.ai` on its own: `createAiNamespace`, `tool` | Your server |
| `@playcode/sdk/email`, `@playcode/sdk/media` | `sendEmail`; `generateImage`, `generateVideo` | Your server |
| `@playcode/sdk/client` | `createAgentChat`, `prepareImage`: the chat for any framework | The browser |
| `@playcode/sdk/react` | `useAgentChat`. React 18 or newer is an optional peer dependency | The browser |
| `@playcode/sdk/testing` | `fakeModel`, `record`, `replay`, `fakeCard`, `agentStoreContract` | Your tests |

The browser entries know only your endpoint's address, never a key.

## The client

`new Playcode(options)` reads each option from its environment variable when you leave it out, at the moment a call needs it:

| Option | Environment variable | What it is |
|---|---|---|
| `apiKey` | `PLAYCODE_SECRET_KEY` | The app's secret key, `pcsk_...`, for AI, email and media |
| `apiToken` | `PLAYCODE_TOKEN` | An access token, `pc_live_...`, for `playcode.api` |
| `aiUrl`, `emailUrl`, `mediaUrl` | `PLAYCODE_AI_URL`, `PLAYCODE_EMAIL_URL`, `PLAYCODE_MEDIA_URL` | The services' addresses. Set them only for a test environment |
| `apiOrigin` | `PLAYCODE_API_ORIGIN` | The API's address; `https://playcode.io` by default |
| `timeoutMs` | | How long a request waits: 10 seconds for email and video; on an AI call, 120 seconds for the answer to start. `playcode.api` calls have none: pass `signal` |
| `fetch` | | The `fetch` every call goes through: a test double or a proxy's |

## A call: generate and stream

| Option | What it does |
|---|---|
| `model` | A model id from `models()`, or a list of ids in fallback order |
| `system` | The instructions |
| `prompt` or `messages` | One user message, or the conversation so far, ending with the user's message |
| `files` | Images and PDFs for the last user message |
| `schema` | A zod schema or JSON Schema: the answer comes back as a checked `object` |
| `tools` | Your functions the model may call |
| `maxSteps` | The most model calls: 10 with tools, 1 without |
| `budget` | `{ credits }`: stop once the calls cost this much |
| `maxOutputTokens` | The longest answer one model call may write |
| `thinking` | `off`, `low`, `medium`, `high` or `max`; the model's default when left out |
| `user` | Your own id for the end user the call serves |
| `cache` | Prompt-cache marks on Claude models: on with tools, off without |
| `brakes`, `context`, `hooks` | See [Agent controls](/docs/developers/sdk-agent-controls) |
| `signal` | An `AbortSignal` that stops the call |

## An agent and its runs

`playcode.ai.agent(config)` takes the options of a call except `prompt`, `messages`, `files`, `user` and `signal`, which belong to each run, and adds these:

| Option | What it does |
|---|---|
| `limits` | `{ user: { creditsPerDay } }`, counted in the store |
| `store`, `name` | Where conversations and durable runs live, and the agent's name in it |
| `executor` | Who runs a durable run's work; this process by default |
| `durable` | `{ leaseMs, heartbeatMs, maxAttempts }` |
| `events` | The bus a durable run's events travel on; this process's memory by default |

A run takes `thread`, `user`, `history`, `files`, `onEvent`, `signal` and `limits`.

| Method | What it does |
|---|---|
| `run(message, options)` | One turn; `run(null, { history })` continues a saved conversation |
| `resume(state, decisions)` | Goes on after an approval |
| `start(message, options)` | A durable run: returns its id at once |
| `get(runId)`, `cancel(runId)`, `events(runId)` | Reads, stops or follows a durable run |
| `resumeRun(runId, decisions)` | Goes on with a durable run that paused for approval |
| `recover()`, `execute(runId)` | Picks up runs a crash left; runs a queued run by its id |
| `history(thread, { user })` | A thread's messages |
| `handler({ user, limits })` | The chat endpoint |
| `asTool({ description })` | The agent as a tool of another agent |

## Events

| Event | When |
|---|---|
| `text` | Answer text as it streams |
| `toolCall` | A complete tool call, before it runs |
| `toolResult` | A tool's result, as soon as it lands |
| `usage` | A model call's usage, and the running total |
| `step` | A model call and its tools are complete |
| `compaction` | Old tool results were cleared, and a brief written if `brief` |
| `fallback` | A model failed without a charge, and the next one takes over |
| `subagent` | A sub-agent finished, with its whole run |
| `done` | The end, with the result |

The events of a durable run also carry a `status` event when its status changes.

## Errors

| Error | When |
|---|---|
| `PlaycodeApiError` | Playcode, or the model's maker, said no: `status`, `code`, `message` in Playcode's own words, `requestId`, `source`, `remainingCredits`, `retryAfterMs`, `usageId` |
| `PlaycodeConnectionError` | No answer came (the network, TLS or a timeout), or a stream broke off mid-answer, which may be charged. `cause` has the original error |
| `PlaycodeConfigError` | The call could not be built: no key or token, no webhook `secret`, a file the model cannot read |
| `ThreadBusyError`, `ThreadOwnerError`, `RunNotPausedError` | A thread has a run in progress; a thread is another user's; a decision came after another one |
| `PlaycodeWebhookError` | A delivery failed its check |
| `PrepareImageError` | The browser could not read a photo |

The codes of a `PlaycodeApiError`, and what to do about each:

| Code | Means | Do |
|---|---|---|
| 402 `insufficient_balance` | The workspace is out of credits | Add credits. Never retry it in a loop |
| 402 `budget_exceeded` | The app's monthly AI budget is spent | The owner raises or clears it; it resets on the 1st (UTC) |
| 429 `rate_limited` | A per-minute limit | The SDK waits out 30 seconds or less and retries; past that, wait `retryAfterMs` |
| 429 `daily_cap` | A daily ceiling on the model's maker is spent | It resets at 00:00 UTC. A fallback model from another maker goes on |
| 413 `request_too_large` | The request is over 32 MiB | Shrink photos, split PDFs, send less text |
| 400 `server_tool_not_supported` | A tool the model's maker runs itself | Write it as one of your functions |
| 422 `invalid_output` | The answer did not match the `schema`; it was paid | Make the schema or the instructions clearer |
| 403 `missing_scope` | The app's key lacks the service | Ask Playcode's agent to switch on AI, or media, for the app |
| 401 `invalid_key` | An old or wrong key | Remove a stale `PLAYCODE_SECRET_KEY` line from `.env.local`: it hides the current key |
| 403 `killed` | Playcode stopped this traffic on purpose | Ask Playcode ([Get help from a person](/docs/help/get-help-from-a-person)); do not work around it. A `model` list moves on to its next model |

An error with `source: "provider"` is the model maker's own, passed on as it was: fix the request it names.

## Defaults and limits

| What | Default or limit |
|---|---|
| Model calls per run | 10 (`maxSteps`) |
| One tool call | 60 seconds (`timeoutMs`) |
| What the model reads of a result | 50,000 characters (`maxResultChars`) |
| Tool calls of one step at once | 4 |
| Retries of an AI call that charged nothing | 2; Playcode's rate limit only when it asks for 30 seconds or less |
| A request's wait | 10 seconds for email and video; 120 seconds for an AI answer to start |
| `job.wait()` for a video | 15 minutes |
| Retries of `playcode.api` | 2, with the same `Idempotency-Key` |
| A webhook delivery's age | 300 seconds |
| A durable run | A lease of 20 seconds, renewed every 5 seconds; it fails after 3 attempts |
| Compaction | Starts at 112,000 tokens of the 160,000-token ceiling; at most 2 folds a run |
| One AI request | 32 MiB, files included |
| One chat message | 25 MB, 10 files, 200,000 characters of text |
| An id: a user, an agent, a thread, a tool | 1 to 64 characters |
| `prepareImage` | At most 2,000 px on the long edge, JPEG |
| Node.js | 22 or newer |
