# Current contract

New integrations use [Agent API V2](/docs/agent-api) and the uniform `/api/v2/agent` prefix. This document preserves the V1 compatibility contract.

# SynthCrew agent contract (v1)

This is the intended, canonical v1 contract for agent clients. The
deterministic artifact at
[`examples/agent-contract-v1.json`](examples/agent-contract-v1.json) is
an allowlisted projection of the live `/api/v1/agent/manifest` and live `/api/v1/agent/openapi.json`
metadata. It is not a second API schema. Use the
live `GET /api/v1/agent/manifest` and `GET /api/v1/agent/openapi.json` responses
for runtime details; the broader
`GET /api/v1/openapi.json` document is for operator inspection.
The committed artifact version is `agent-capabilities.v1`.

Only the routes in the artifact are canonical agent routes. A route that is
present elsewhere in the application but absent from this matrix is not part of
the v1 agent contract and must not be advertised or called with an agent key.

## Discovery and authentication

The canonical discovery endpoints are:

- `GET /api/v1/agent/manifest`
- `GET /api/v1/agent/capabilities`
- `GET /api/v1/agent/openapi.json`

Authenticate agent routes with a workspace-scoped key in
`X-Agent-API-Key`. Keys are grant-filtered: an agent sees and uses only the
capabilities and routes allowed by its scopes, and never crosses workspace
boundaries. Bearer tokens, legacy API keys, missing, revoked, expired, and
cross-workspace credentials are rejected. A valid key without a required grant
gets `403 SCOPE_MISSING`.

Credentials, OAuth authorization codes, provider tokens, and webhook secrets
must never appear in examples, request bodies, or agent responses.

### URL, route, and response semantics

The manifest is intentionally explicit about URL construction. `api.canonical_origin`
is the host only; append `api.discovery_base_path` for discovery and
`api.resource_base_path` for scoped resource routes. The older
`contract.canonical_base_url` value is retained for v1 clients but is deprecated
because it combines a host and namespace.

Every route in the live manifest includes a stable `operation_id`, a readable
`label` and `description`, its `category`, `namespace`, `access_mode`, and
`effect`. The `/api/v1/agent` namespace contains discovery and governed
operations. Scoped legacy-compatible resources such as `/api/campaigns` and
`/api/prospects` remain valid agent routes and are listed explicitly; a route
not present in the filtered manifest is not available to that key.

Agent errors use `application/problem+json` and the RFC-7807-compatible shape
`type`, `title`, `status`, `detail`, `instance`, optional stable `code`,
optional `fields`, and optional `reset_at`. Branch on `status` and `code`, not
on prose. Older clients may still receive or send the legacy nested `detail`
shape during the v1 compatibility window.

## Inspect billing capacity before action

Every paid plan includes the complete product and Agent API capability set.
Launch, Growth, and Scale differ only in connected-mailbox and workspace
Daily-send capacity:

| Plan | Monthly / annual | Mailboxes | Daily sends |
| --- | --- | ---: | ---: |
| Launch | $49 / $490 USD | 5 | 50 |
| Growth | $99 / $990 USD | 25 | 300 |
| Scale | $249 / $2,490 USD | 100 | 1,500 |

Before connecting a mailbox, scheduling, launching, or sending, read:

```http
GET /api/billing/usage
```

The endpoint accepts either the current human Bearer session or a scoped
`X-Agent-API-Key` workspace principal. Inspect both counters before constructing
capacity-sensitive work:

```json
{
  "plan_key": "synthcrew_growth",
  "access_mode": "full",
  "limits": { "mailboxes": 25, "daily_sends": 300 },
  "usage": {
    "mailboxes": { "used": 12, "limit": 25, "remaining": 13 },
    "daily_sends": {
      "used": 175,
      "limit": 300,
      "remaining": 125,
      "reset_at": "2026-08-08T00:00:00Z"
    }
  }
}
```

Do not infer remaining capacity from a plan name, marketing page, checkout
redirect, or cached value. Do not split, duplicate, or retry work to evade a
workspace limit.

### Typed billing and capacity recovery

Error responses preserve an HTTP status, stable `code`, human-readable
`message`, and relevant recovery metadata. Branch on the status and `code`;
do not parse the message.

| Status | Meaning | Required agent recovery |
| --- | --- | --- |
| `402 subscription_required` | A paid subscription is required or access is not billable. | Preserve the intended action, stop mutation, and ask the workspace owner to choose a plan in Billing. Re-read summary and usage after confirmation. |
| `403 billing_read_only` | The authenticated member or agent can inspect billing but cannot mutate it. | Do not request broader or borrowed credentials. Ask a workspace owner to perform checkout or a plan change. |
| `409 workspace_mailbox_limit_reached` | Connecting another mailbox would exceed plan capacity. | Do not retry. Ask an owner to disconnect an unused mailbox or complete a plan change, then re-read usage. A billing-change-pending `409` likewise requires waiting for reconciliation instead of duplicate submission. |
| `429 workspace_daily_send_limit_reached` | The current plan's daily-send capacity is exhausted. | Stop the affected action. Honor `Retry-After`, re-read usage and `reset_at`, and retry only after reset or after an owner-completed plan change is confirmed. |

A `429` is not authorization to create an overage. A `409` is not
authorization to resubmit checkout. Billing checkout, plan change, portal,
cancellation, and payment methods remain owner-only human actions; non-owner
users and agents receive a read-only billing view.

## Human-only administration and Google OAuth

Agent key administration is a human-only workspace surface. A current
workspace owner or administrator uses these canonical routes:

| Action | Canonical route |
| --- | --- |
| Create | `POST /api/agent/keys/workspaces/{workspace_id}/keys` |
| List metadata | `GET /api/agent/keys/workspaces/{workspace_id}/keys` |
| Revoke | `DELETE /api/agent/keys/workspaces/{workspace_id}/keys/{key_id}` |
| Rotate | `POST /api/agent/keys/workspaces/{workspace_id}/keys/{key_id}/rotate` |

These routes require the current human workspace context. An agent key cannot
create, list, revoke, or rotate keys. Create returns a one-time `raw_key`;
the caller must store it outside SynthCrew because it cannot be recovered.
List and revoke return redacted metadata only: never a hash or raw secret.
Successful rotation returns the replacement `raw_key` one time; an idempotent
retry replays rotation metadata and does not issue the raw secret again.
Rotation metadata is recursively redacted and limited to 16 KiB of serialized
JSON. Oversized input is rejected with `422` before mutation, and an oversized
persisted response fails closed with `503
AGENT_KEY_ROTATION_RESOURCE_BOUND`. The complete rotation response is limited
to 64 KiB.

Google mailbox acquisition and provider consent are also human-only. The
current human workspace context uses the canonical routes below; an agent key
cannot start OAuth, receive an authorization code, grant provider approval, or
refresh, reconnect, or revoke the provider credential:

- `POST /api/email-accounts/google/start`
- `GET /oauth/google/authorize`
- `GET /oauth/google/callback` (provider redirect)
- `POST /api/email-accounts/{account_id}/oauth/google/refresh`
- `POST /api/email-accounts/{account_id}/oauth/google/reconnect`
- `POST /api/email-accounts/{account_id}/oauth/google/revoke`

The OAuth callback is a provider redirect, not an agent endpoint. OAuth state,
authorization codes, refresh tokens, and mailbox credentials remain
server-side. This is the Google BYOK path: the workspace owns and authorizes
its configured connection. Google consent is a human approval step;
`X-Agent-API-Key` is not an alternative to it. OpenAPI marks the OAuth start,
authorize, and mailbox lifecycle operations as `x-agent-access: human_only`.

Human-role endpoints do not downgrade an agent key to a default member role.
Agent access exists only where this contract declares an explicit scoped-agent
route. Destructive privacy erasure is not an agent capability in v1; a request
carrying `X-Agent-API-Key` is rejected with
`403 AGENT_ERASURE_UNSUPPORTED`.

## Capability vocabulary

The artifact contains the following capability names and grants. A capability
without a route in the matrix is vocabulary only; it is not a promise that a
callable endpoint exists.

| Capability | Required scope |
| --- | --- |
| `automations.execute` | `automations:execute` |
| `campaigns.launch` | `campaigns:launch` |
| `campaigns.read` | `campaigns:read` |
| `campaigns.write` | `campaigns:write` |
| `capabilities.read` | none |
| `dnc.read` | `dnc:read` |
| `dnc.write` | `dnc:write` |
| `drafts.read` | `drafts:read` |
| `drafts.write` | `drafts:write` |
| `events.read` | `receipts:read` |
| `exports.read` | `exports:read` |
| `growth.activation.read` | `growth:read` |
| `lead_vault.enrich` | `leads:write` |
| `lead_vault.export` | `exports:write` |
| `lead_vault.lists.read` | `leads:read` |
| `lead_vault.merge` | `leads:write` |
| `lead_vault.refresh` | `leads:write` |
| `lead_vault.search` | `leads:write` |
| `lead_vault.search.quote` | `leads:read` |
| `lead_vault.verify` | `leads:write` |
| `leads.read` | `leads:read` |
| `leads.write` | `leads:write` |
| `mailboxes.read` | `mailboxes:read` |
| `operations.approve` | `automations:approve` |
| `operations.cancel` | `automations:execute` |
| `operations.execute` | `automations:execute` |
| `operations.preview` | `automations:execute` |
| `operations.read` | `receipts:read` |
| `privacy.read` | `privacy:read` |
| `provider_budget.configure` | `providers:write` |
| `provider_budget.read` | `providers:read` |
| `provider_usage.read` | `providers:read` |
| `receipts.read` | `receipts:read` |
| `replies.read` | `replies:read` |
| `replies.write` | `replies:write` |
| `sequences.read` | `sequences:read` |
| `sequences.write` | `sequences:write` |
| `settings.read` | `settings:read` |
| `settings.write` | `settings:write` |
| `templates.read` | `templates:read` |
| `templates.write` | `templates:write` |
| `warmup.read` | `warmup:read` |
| `warmup.write` | `warmup:write` |
| `webhooks.manage` | `webhooks:manage` |



### Lead Vault and provider-economics tools

The `/api/v1/agent/lead-vault` routes are workspace-scoped adapter boundaries.
They never infer another workspace from a request body: the authenticated agent
key supplies the workspace identity. Search quote/execute, lead
enrich/verify/refresh, merge, export, saved-list reads, and provider
usage/budget operations are published with the exact scopes in the matrix below.
Unsupported saved-search and list-mutation handlers remain internal
compatibility routes and are not advertised to agents.

Every mutation requires `Idempotency-Key` and `X-Agent-Run-Id` and returns a
validated `receipt_id`. Search execution, enrichment, verification, refresh,
merge, export, and provider-budget configuration are chargeable or high-impact:
use `X-Dry-Run: true` for preview or provide `X-Human-Approval-Id` for live
execution. Unsupported saved-search and list mutations are not advertised and return a
typed unavailable response if called directly.

The HTTP contract delegates to the installed workspace Lead Vault integration.
If that integration is absent, the route fails closed with typed
`LEAD_VAULT_ADAPTER_UNAVAILABLE` (`503`) rather than claiming provider work.
Other stable failures include `SCOPE_MISSING`, `DRY_RUN_OR_APPROVAL_REQUIRED`,
`INVALID_IDEMPOTENCY_KEY`, `INVALID_AGENT_RUN_ID`, provider budget/rate `429`,
and `LEAD_VAULT_RECEIPT_INVALID`. Responses are bounded to 64 KiB and reject
secret-bearing fields.

### Workspace growth activation read

`GET /api/v1/agent/growth/activation` publishes
`growth.activation.read` to keys with the exact `growth:read` scope. The key's
verified active workspace is authoritative; the route accepts no query/body,
workspace selector, platform aggregate selector, or experiment selector. It
returns only the exact `growth.activation.v1` workspace projection and fails
closed with typed `503 GROWTH_ACTIVATION_UNAVAILABLE` or
`GROWTH_ACTIVATION_CONTRACT_INVALID` when the installed service is absent,
fails, or returns an over-broad response.

The read performs no provider call, OAuth action, experiment assignment, email,
send, billing mutation, or receipt mutation. Human consent and provider OAuth
remain separate human-controlled boundaries; no raw identity, credential,
visitor/event payload, attribution, revenue, or platform aggregate is exposed.
Because this is a `GET`, `Idempotency-Key`, `X-Agent-Run-Id`, dry-run, and
approval headers are not required or consumed. See
[`growth-agent-activation.md`](growth-agent-activation.md) for the response,
privacy, retry, and integration contract.

### Controlled Gmail delivery

`POST /api/inbox/controlled-delivery` is the only agent-visible live Gmail
send/reply boundary. It requires all of the following on the same request:

- `X-Agent-API-Key` with `replies:write`;
- a verified owner/admin Clerk bearer session in `Authorization` for the same
  workspace;
- `Idempotency-Key`, `X-Agent-Run-Id`, and a UUID `X-Human-Approval-Id`;
- an active healthy connected Google BYOK sender and a server-owned,
  explicitly allowlisted recipient that is not a prospect or suppression/DNC
  match.

The provider is called at most once. An ambiguous provider outcome is replayed
from the durable receipt and must not be retried with a new key. Google OAuth
connect/reconnect and the human approval event remain human-only.

## Canonical route and scope matrix

These method/path/scope rows are generated from the live scoped route metadata:

| Route | Required scope |
| --- | --- |
| `GET /api/automation/history` | `receipts:read` |
| `GET /api/campaigns` | `campaigns:read` |
| `POST /api/campaigns` | `campaigns:write` |
| `GET /api/campaigns/{campaign_id}` | `campaigns:read` |
| `PATCH /api/campaigns/{campaign_id}` | `campaigns:write` |
| `POST /api/campaigns/{campaign_id}/cancel` | `campaigns:launch` |
| `POST /api/campaigns/{campaign_id}/launch` | `campaigns:launch` |
| `GET /api/campaigns/{campaign_id}/launch-readiness` | `campaigns:read` |
| `POST /api/campaigns/{campaign_id}/pause` | `campaigns:launch` |
| `POST /api/campaigns/{campaign_id}/resume` | `campaigns:launch` |
| `POST /api/campaigns/{campaign_id}/schedule` | `campaigns:launch` |
| `GET /api/email-accounts` | `mailboxes:read` |
| `GET /api/email-accounts/inventory` | `mailboxes:read` |
| `GET /api/export/{resource}` | `exports:read` |
| `GET /api/inbox` | `replies:read` |
| `POST /api/inbox/controlled-delivery` | `replies:write` |
| `POST /api/inbox/{inbox_id}/archive` | `replies:write` |
| `GET /api/inbox/{inbox_id}/thread` | `replies:read` |
| `GET /api/leads/archived` | `leads:read` |
| `POST /api/leads/bulk` | `leads:write` |
| `POST /api/leads/import` | `leads:write` |
| `POST /api/leads/{prospect_id}/restore` | `leads:write` |
| `GET /api/prospects` | `leads:read` |
| `POST /api/prospects` | `leads:write` |
| `GET /api/prospects/{prospect_id}` | `leads:read` |
| `PATCH /api/prospects/{prospect_id}` | `leads:write` |
| `GET /api/v1/agent/capabilities` | none |
| `POST /api/v1/agent/lead-vault/exports` | `exports:write` |
| `POST /api/v1/agent/lead-vault/leads/merge` | `leads:write` |
| `POST /api/v1/agent/lead-vault/leads/{lead_id}/{action}` | `leads:write` |
| `POST /api/v1/agent/lead-vault/leads/{lead_id}/{action}/quote` | `leads:read` |
| `GET /api/v1/agent/lead-vault/lists` | `leads:read` |
| `GET /api/v1/agent/lead-vault/providers/budgets` | `providers:read` |
| `PUT /api/v1/agent/lead-vault/providers/budgets` | `providers:write` |
| `GET /api/v1/agent/lead-vault/providers/usage` | `providers:read` |
| `POST /api/v1/agent/lead-vault/search` | `leads:write` |
| `POST /api/v1/agent/lead-vault/search/quote` | `leads:read` |
| `GET /api/v1/agent/growth/activation` | `growth:read` |
| `GET /api/v1/agent/manifest` | none |
| `GET /api/v1/agent/operations` | `receipts:read` |
| `POST /api/v1/agent/operations/preview` | `automations:execute` |
| `GET /api/v1/agent/operations/{operation_id}` | `receipts:read` |
| `POST /api/v1/agent/operations/{operation_id}/approve` | `automations:approve` |
| `POST /api/v1/agent/operations/{operation_id}/cancel` | `automations:execute` |
| `GET /api/v1/agent/operations/{operation_id}/events` | `receipts:read` |
| `POST /api/v1/agent/operations/{operation_id}/execute` | `automations:execute` |
| `GET /api/v1/agent/operations/{operation_id}/receipt` | `receipts:read` |
| `GET /api/v1/agent/receipts/{receipt_id}` | `receipts:read` |
| `GET /api/v1/agent/self-test` | none |
| `GET /api/v1/agent/webhooks` | `webhooks:manage` |
| `POST /api/v1/agent/webhooks` | `webhooks:manage` |
| `GET /api/v1/agent/webhooks/deliveries/{delivery_id}` | `receipts:read` |
| `GET /compliance/consent/{lead_id}` | `privacy:read` |
| `GET /dnc` | `dnc:read` |
| `POST /dnc` | `dnc:write` |
| `POST /dnc/bulk` | `dnc:write` |
| `DELETE /dnc/{entry_id}` | `dnc:write` |
| `GET /drafts` | `drafts:read` |
| `POST /drafts/{draft_id}/edit` | `drafts:write` |
| `GET /mailboxes/health/summary` | `mailboxes:read` |
| `GET /mailboxes/{mailbox_id}/deliverability/snapshot` | `mailboxes:read` |
| `GET /mailboxes/{mailbox_id}/health` | `mailboxes:read` |
| `GET /mailboxes/{mailbox_id}/warmup` | `warmup:read` |
| `PATCH /mailboxes/{mailbox_id}/warmup` | `warmup:write` |
| `GET /mailboxes/{mailbox_id}/warmup/events` | `warmup:read` |
| `GET /sequences` | `sequences:read` |
| `POST /sequences` | `sequences:write` |
| `DELETE /sequences/{sequence_id}` | `sequences:write` |
| `GET /sequences/{sequence_id}` | `sequences:read` |
| `PATCH /sequences/{sequence_id}` | `sequences:write` |
| `PUT /sequences/{sequence_id}/layout` | `sequences:write` |
| `POST /sequences/{sequence_id}/steps` | `sequences:write` |
| `PATCH /sequences/{sequence_id}/steps/{step_id}` | `sequences:write` |
| `GET /templates` | `templates:read` |
| `POST /templates` | `templates:write` |
| `GET /templates/popular` | `templates:read` |
| `DELETE /templates/{template_id}` | `templates:write` |
| `GET /templates/{template_id}` | `templates:read` |
| `PATCH /templates/{template_id}` | `templates:write` |

The approval route is dual-control: it requires both the workspace-scoped agent key and an independently verified owner/admin Clerk bearer session. An agent key alone cannot approve its own preview.

Mutating canonical routes require the action envelope declared by the
artifact. In particular, `POST /api/v1/agent/operations/preview`,
`POST /api/v1/agent/operations/{operation_id}/approve`,
`POST /api/v1/agent/operations/{operation_id}/cancel`,
`POST /api/v1/agent/operations/{operation_id}/execute`, and
`POST /api/v1/agent/webhooks` require `Idempotency-Key` and
`X-Agent-Run-Id`.

The unversioned `GET /api/agent/manifest` compatibility alias is not the
canonical discovery route for new clients. It is retained only as an
authenticated, scope-filtered migration alias and is excluded from served
contract documents; callers must still provide a workspace-scoped
`X-Agent-API-Key`. The workspace routes above are explicitly scoped; do not
infer agent access to any other route from a generic key.

## Action envelope and lifecycle

The live action envelope advertises these headers for mutating actions:

| Header | Required | Meaning |
| --- | --- | --- |
| `Idempotency-Key` | yes | Stable key for idempotent mutation replay. |
| `X-Agent-Run-Id` | yes | Agent run identifier carried into provenance. |
| `X-Dry-Run` | no | Preview without applying side effects. |
| `X-Human-Approval-Id` | no | Approval reference for gated execution. |

`Idempotency-Key` is scoped to workspace and request hash. Reuse the same key
for a safe retry of the same request; use a new key for a different action.
`Idempotency-Key` is at most 255 characters. `X-Agent-Run-Id` and
`X-Human-Approval-Id` are at most 128 characters each, and control characters
are rejected with `INVALID_ACTION_ENVELOPE`.

Execution requires an approved preview and a matching human approval
reference. The lifecycle states served by the artifact are `preview`,
`approved`, `queued`, `running`, `succeeded`, `failed`, and `cancelled`.
The approval route records the approval reference only when an owner/admin
Clerk bearer session is present alongside the bound agent key. An agent key
alone receives `HUMAN_APPROVAL_SESSION_REQUIRED`; it cannot self-approve. This
also does not replace human authorization for Google OAuth or provider consent.

The safe operation flow is preview, approve, execute, then read the operation
or terminal receipt:

```bash
BASE="https://api.synthcrew.io"
KEY="<workspace-scoped-agent-key>"
RUN="agent-run-42"
HUMAN_SESSION="<verified-owner-or-admin-Clerk-JWT>"

curl -sS "$BASE/api/v1/agent/operations/preview" \
  -H "X-Agent-API-Key: $KEY" \
  -H 'Idempotency-Key: operation-42-preview' \
  -H "X-Agent-Run-Id: $RUN" \
  -H 'X-Dry-Run: true' \
  -H 'Content-Type: application/json' \
  --data '{"intent":"preview","workflow_id":"wf-42","workflow_name":"local-check","steps":[]}'

curl -sS "$BASE/api/v1/agent/operations/<operation_id>/approve" \
  -H "Authorization: Bearer $HUMAN_SESSION" \
  -H "X-Agent-API-Key: $KEY" \
  -H 'Idempotency-Key: operation-42-approve' \
  -H "X-Agent-Run-Id: $RUN" \
  --data '{"approval_id":"human-approval-42","reason":"approved local sandbox plan"}'

curl -sS "$BASE/api/v1/agent/operations/<operation_id>/execute" \
  -H "X-Agent-API-Key: $KEY" \
  -H 'Idempotency-Key: operation-42-execute' \
  -H "X-Agent-Run-Id: $RUN" \
  -H 'X-Human-Approval-Id: human-approval-42'
```

The preview endpoint accepts only `intent: preview`. Built-in steps are
deterministic local sandbox steps: they do not call providers, networks,
Celery, webhooks, live-send, or billing. A local receipt is not production
delivery proof. An operation page is capped at 64 KiB of serialized JSON; an
oversized persisted result fails closed with `503
AGENT_OPERATION_RESOURCE_BOUND`.

## Pagination, errors, and recovery

Operation events provide bounded pagination and resumable reads. `limit` is
`1`–`100`, and `after_sequence` is a monotonic per-operation resume point that
defaults to `0`. The response returns `next_after_sequence` when more events
exist. `422 EVENT_SEQUENCE_INVALID` rejects invalid input, while
`409 EVENT_SEQUENCE_GAP` fails closed for an invalid resume point or broken
sequence continuity. Event pages preserve workspace, operation/receipt,
request-hash, sequence, and event-hash provenance.

`GET /api/v1/agent/operations/{operation_id}/receipt` returns
`409 RECEIPT_NOT_TERMINAL` until the operation is `succeeded`, `failed`, or
`cancelled`. The common boundary is `401` for invalid authentication, `403`
for missing scope or actor/workspace binding, `404` for an absent
workspace-scoped operation or delivery, `409` for approval, idempotency, or
lifecycle conflicts, and `422` for invalid bounded input. Retry only after
inspecting the typed error and preserving the same idempotency key.

Webhook delivery status is redacted and workspace-bound. It exposes
`status`, `attempt_count`, typed `error_code`, stable `idempotency_key`, and
event/operation provenance. Raw `last_error`, destination URLs, payloads,
provider details, and webhook secrets are never returned. A `pending` delivery
is an intent, not delivery proof; poll the delivery route after a timeout or
consumer restart.


## Read-only self-test

After a key is connected, an agent may call `GET /api/v1/agent/self-test`. It
requires the same `X-Agent-API-Key` as the manifest and returns the
scope-filtered tools/routes plus checks for authentication, workspace binding,
scope filtering, and contract availability. A successful response has
`contract_version: agent-capabilities.v1` and `status: ready`.

The self-test is deliberately side-effect free. Its safety section states that
no provider call, mail send, or billing mutation was performed. It is a
preflight, not permission to skip preview, human approval, idempotency,
workspace isolation, or provider consent. Retryable transport statuses are
429/502/503/504 and clients should honor `Retry-After`.

## SDKs

The dependency-free reference clients live under [`sdks/`](../sdks/):

- Python 3.11: `sdks/python/synthcrew_agent.AgentClient`
- TypeScript/browser or Node: `sdks/typescript`

Both clients expose self-test and manifest discovery and require
`Idempotency-Key` and `X-Agent-Run-Id` for every mutation. Neither client
prints or persists the agent key, and neither can generate a human approval.
