AI agents and LLMs

An agent integrating Inbox should not have to read fifty documentation pages, and should not have to guess. Everything here exists so an agent can go from "no messaging" to a working integration in one pass, and so a human can tell which parts of what it read are trustworthy.

/llms.txt is the entry point

Every deployed instance serves /llms.txt — the conventional index for a machine reading a site. It carries the site's shape, links to the integration skill, and the handful of rules that account for most integration failures.

It is generated from the same navigation that builds the sidebar, so a page added to the docs appears in it without anyone remembering to update a list. A hand-maintained index of fifty pages would be wrong within a month.

curl -s https://inbox.example.com/llms.txt

The integration skill

The skill is published verbatim at /skill/, so an agent can fetch and follow it directly rather than needing a human to have installed it first.

File Contents
/skill/SKILL.md The onboarding runbook
/skill/scripts/onboard.mjs Idempotent end-to-end onboarding script; supports --dry-run and --json
/skill/references/config.example.json The script's input format
/skill/references/authentication.md Credentials, tenancy, the scope taxonomy, operator access
/skill/references/consuming-the-api.md Envelopes, idempotency, pagination, retries, the SDK surface
/skill/references/receiving-events.md WebSocket, SSE and outbound webhooks
/skill/references/channels.md Connecting WhatsApp, Meta, email, SMS and live chat
/skill/references/manual-onboarding.md The raw curl sequence, for debugging what the script did
/skill/references/troubleshooting.md Every failure code and its real cause

To install it as a Claude Code skill:

INBOX=https://inbox.example.com
DEST=~/.claude/skills/inbox-integration
mkdir -p $DEST/references $DEST/scripts
for f in SKILL.md scripts/onboard.mjs \
         references/config.example.json references/authentication.md \
         references/consuming-the-api.md references/receiving-events.md \
         references/channels.md references/manual-onboarding.md \
         references/troubleshooting.md; do
  curl -sS -o "$DEST/$f" "$INBOX/skill/$f"
done

onboard.mjs performs the whole chain in order, because each step's output feeds the next: register the app in Accounts, publish the inbox:* scope catalogue, mint a scoped sk_ key (shown once), resolve the tenant, then verify — call GET /v1/whoami and assert the resolved app, mode, scopes and tenant are what was asked for, then list threads to prove the tenant resolves. That last step matters more than it looks: a key that authenticates but resolves the wrong app, and a tenant that silently provisioned empty, both look like success.

Generated versus hand-written, and why you should care

Four reference pages are generated from the service's own source at build time and carry a notice saying so:

They cannot drift from the running service, because they are the running service, rendered. When an agent needs a route, a scope name, an env var or an error code, those four pages are the ones to trust without verification.

Everything else — including this page — is hand-written prose about why things behave as they do. It is reviewed against source, but it is not mechanically guaranteed. And the docs ship in the same release as the code they describe, so a page's age is the deployment's age.

The corollary matters for tool use: prefer the generated reference for facts, and prose for judgement. If the two ever disagree, the generated page is right.

The facts agents most often get wrong

Unwrap on the presence of data, not its truthiness. {"data": null} is a legitimate answer. json?.data ?? json returns the whole envelope — a truthy object — and a consumer that reads a field off it silently gets undefined. This shipped once as "you have used your free SMS" when the truth was "we could not tell you".

Idempotency-Key is required by HTTP verb, not per route. Every POST, PUT, PATCH and DELETE needs one by default, so several POSTs that only read still demand it — /v1/templates/verify-media-url, the /v1/email-domains/* and /v1/email-validation/* routes, the flow simulators, /v1/realtime/tokens. Three routes opt out: /v1/campaigns/preflight, /v1/reports/query, /v1/reports/export. Do not infer the rule from a route's semantics.

admin satisfies everything on tenant routes and nothing on operator routes. Cross-tenant reads need stats explicitly; cross-tenant writes need operator:connections:write on top. An agent that reasons "admin is the strongest scope, so it must work here" is wrong on /v1/operator/*.

A wrong tenant does not error — it provisions. Tenant resolution is an upsert on (appId, externalRef). A typo, a local database id, or last environment's workspace id all return 200 with a brand-new empty workspace. "The API works but there is no data" is almost always this. Verify with whoami plus a list call, not with whoami alone.

Threads cannot be created. There is no create-thread endpoint and there will not be one. A thread appears when a message arrives, through ingest. Anything that looks like "start a conversation" is a template send (POST /v1/connections/:id/send-template) or a campaign.

The SDK README is stale. @~inbox/sdk's published README documents the retired axs_ key format, shows tenant as required and omits session entirely. The client now hard-rejects any key that does not start sk_. Use The Node SDK; do not use the README.

Reporting a problem

Every response carries meta.requestId, echoed as the x-request-id header. An agent filing an issue should quote it — it is the only handle that ties a client-side symptom to a server-side log line.