Skip to main content
Code Mode is a second endpoint of the hosted MCP server. Instead of the 139 tools, the agent gets two: describe, which returns the TypeScript declarations of the API, and execute, which runs a script the agent wrote. Inside the script, each call is await ps.<tool>(args) — the same tools, the same arguments and the same permissions as on /mcp.
The /mcp endpoint does not change. Pick one per connection.

When to use it

Use /code when:
  • the task chains several calls — list, filter, then read or act on each item;
  • you loop over many items (numbers, webhooks, messages, contacts);
  • you want a report computed from many reads, returned as one result;
  • your agent’s context is tight and the 139 tool definitions cost too much.
Stay on /mcp when the task is one or two calls, or when you want to approve every call one by one in your client. A chain that takes dozens of round trips through the model on /mcp is a single execute on /code: the intermediate results stay inside the script, and only what it prints comes back to the model.

What it costs in context

Measured with count_tokens on Claude Sonnet: The agent only pays for the themes it actually reads.

Connect

Connect either /mcp or /code to an agent, not both. With both connected, the agent still loads the 139 tool definitions of /mcp (77,666 tokens when there were 114), and the context saving of /code is gone. If you already use /mcp, turn that connector off in the agent where you add /code.
Authentication is the same as on /mcp: an OAuth 2.1 token (Authorization: Bearer, handled by the claude.ai custom connector, with whole-tenant or per-number grants) or a ps_* API key in the x-api-key header — with an account key (ps_account_), plus x-workspace-id naming the workspace (details). Each call the script makes goes to the public /v1 API with that credential, so the key’s scope, roles, per-number grants, rate limits and billing apply exactly as they do on /mcp. In claude.ai, add a custom connector with the URL https://mcp.pilotstatus.com.br/code. Clients configured by file:
Code Mode exists only on the hosted server. The npm package @pilot-status/mcp-server (self-hosted) serves the 139 tools only.

describe

Returns what the script can call. It takes at most one argument: Themes: messages, conversations, referrals, leads, ads, groups, numbers, templates, flows, branding, analytics, api-keys, subscription, media, billing, webhooks, calls, chatwoot, pricing, workspace, phone-lines, docs. Every method except docs_search also accepts whatsappNumberId, the target number for number-scoped tools. It is required with an OAuth or tenant-scoped credential (it is sent as the x-whatsapp-number-id header) and ignored when the API key is already scoped to one number. Errors: passing both theme and search returns INVALID_ARGUMENTS; an unknown theme returns UNKNOWN_THEME with the list of valid themes.

execute

Takes one argument, code: the body of a TypeScript script.
  • Top-level await works. Call the API with await ps.<tool>(args); the result is the API’s JSON response.
  • Print with console.log (also info, warn, error, debug). Objects are printed as JSON.
  • A return value is appended to the output as => <value>.
  • Types are only stripped, not checked: a type error does not stop the script, a syntax error does.
  • There is no network and nothing from the host: no fetch, no import, no require, no process, no setTimeout/setInterval. The only way out of the script is ps.

Example: list, then read each

Reads the recent deliveries of every webhook on the number, in parallel, and prints only the summary.

Example: handle an API refusal

When the API refuses a call, ps.<tool> throws inside the script. The error has name === "ApiError", the HTTP status and the API’s code. Catch it to keep going.
An uncaught error stops the script. Other errors a call can throw: InvalidArguments (the arguments do not match the declaration; code is INVALID_ARGUMENTS), CallLimitExceeded and SendLimitExceeded (see Limits).

Example: send safely with idempotencyKey

Pass an idempotencyKey to every ps.messages_send. Derive it from what makes the send unique — here the order id and the recipient — so that running the same script again does not send the same message twice.
Within 24 hours, the same key with the same arguments returns the first accepted response and sends nothing. The same key with different arguments is 409 IDEMPOTENCY_KEY_REUSED. Idempotency

Writing scripts that run fast

Each ps call takes seconds, and the 30-second clock counts them all. The description of execute — what the agent reads in the tool list — tells it to:
  • Reuse ids it already has. With a workspace-wide credential, pass whatsappNumberId to every number-scoped call, and reuse the id (and any other id) already obtained earlier in the conversation. Call ps.numbers_list({}) only when the id is not known yet.
  • Run independent calls together with await Promise.all([...]), never await one by one in a loop — as in the first example.
  • Ask only for what it needs: a small pageSize and filters such as status, startDate and search.
  • Use a real URL for media: an http(s) URL it was given, never invented base64. For audio and voice this is enforced: a data URI is refused with 400 MCP_AUDIO_URL_REQUIRED and nothing is sent. MCP server
The same applies to scripts you write or review yourself.

Limits per run

  • Every call counts, refused ones included. A messages_send with invalid arguments still counts as a send.
  • Only messages_send counts as a send. call_initiate and call_permission_request count only in the 50 calls.
  • There is no hourly limit on runs: the rate limits of the /v1 API apply to each call, as they do on /mcp.

Failure and the report

A failure stops the script, and nothing is undone. What was sent stays sent. Every execute that ran returns a report:
calls is how the agent knows what already happened before deciding what to do next. To run a script again safely, it must pass idempotencyKey to every ps.messages_send — without it, a re-run sends again.

Refused before running

When the script cannot start, execute returns isError: true with an error, a code and no report: With no credential at all, /code answers 401 and starts the OAuth flow, as /mcp does. A credential the API rejects refuses the run with the API’s status and code.

What is recorded

Each run — the script, the arguments of every call and the output — is kept for 30 days for support and security, then deleted. Arguments and output can contain contact data (phone numbers, message text). Runs are not available in the dashboard or the API: the report returned by execute is your copy.