# Platform API — the `/v1` surface

The tenant-keyed HTTP API a third-party app builds on. JSON in, JSON out, over HTTPS. This page is the map; the
**machine-readable spec is canonical** — point tooling at it:

- Production spec (only ops callable today): `https://api.witbitz.chat/v1/openapi.json` (`.yaml`)
- Full design spec (every op, `x-status`-labelled): `https://api.witbitz.chat/v1/openapi.full.json`
- Discovery index: `https://api.witbitz.chat/v1/`

> The **Bridge** is a *separate* contract (party signatures, not tenant keys) — see [the Bridge](./the-bridge.md).

---

## Base + envelope

- **Base URL:** `https://api.witbitz.chat/v1`
- **Success** is enveloped `{ "data": <payload>, "meta"?: {...} }`; **errors** are
  `{ "error": { "code", "message", "request_id"? } }`. One exception: `POST /space` (`x-envelope: false`) returns a
  raw payload and a flat error shape — unifying it is a tracked follow-up.
- Version `1.0.0-beta`. Private beta — the contract may change (coordinated with invited integrators) until GA.

## Authentication

Two tenant API keys, resolved by the `/v1` authorizer to a `tenantId`:

- **Secret key** `wsk_` — full scope, **server-side only**.
- **Publishable key** `wpk_` — browser-safe, public reads + funnel-entry writes. It *identifies* a tenant; it does
  **not** authenticate an end user.
- **Per-collection capability tokens** (`x-collection-token`): the owner `admin_token` or the add-only
  `contributor_token`, for owner/contributor actions on a collection. Wallet/license credentials travel in the
  request **body**, never a URL.

Capability tokens + raw keys are shown **once** at creation and stored only as a hash (or KMS-sealed). Store them
server-side; lost = re-create.

## Operational contract

- **Idempotency:** money/billable ops (`POST /sessions`, `/wallets/grant`, `/collections/{id}/paint`) require an
  `Idempotency-Key`; same key + same body replays the first 2xx byte-for-byte.
- **Rate limiting + kill switch:** throttled at the gateway; per-tenant/per-key-type limits at the authorizer; an
  operator can suspend a tenant (`POST /tenants/{tenant_id}/status`) — every key is denied within ~15s. That op and
  `POST /tenants` are **operator-only** (`X-Admin-Token`, not a tenant key) and are deliberately absent from the
  published spec, so don't go looking for them in `openapi.json`.
- **Secret handling:** one-time-secret responses are `Cache-Control: no-store`; sensitive fields `x-sensitive`;
  BYOK provider keys + the webhook secret are KMS-sealed at rest.

## The surface (by area)

**Agents**
- `GET /catalog` — the public agent catalog (publishable).

**Collections** — the generic content primitive (opaque metadata + items + curation + a keyless render page)
- `POST /collections` · `GET /collections/{id}` · `DELETE /collections/{id}`
- `GET /collections/{id}/render` — **keyless** share page for published content (public)
- items: `GET/POST /collections/{id}/items` · `PATCH/DELETE /collections/{id}/items/{itemId}`
- `POST /collections/{id}/state` — merge opaque app state onto the collection

**Artifacts** — produced media on a collection
- `GET/POST /collections/{id}/artifacts` · `GET/PATCH/DELETE .../artifacts/{artifactId}`
- `POST /collections/{id}/paint` · `POST /collections/{id}/produce`
- `POST /collections/{id}/artifacts/upload` → presigned S3 PUT, then
  `POST /collections/{id}/artifacts/{artifactId}/ready` to stamp it complete — both halves are required

**Sessions**
- `POST /sessions` — launch (or re-summon) an agent; metered, idempotent. Accepts **either** key: the spend
  credential is the coupon in the body, so a publishable key launches nothing without a funded wallet.

**Credits**
- `POST /wallets/lookup` · `POST /wallets/redeem` · `POST /wallets/grant` · `POST /checkout` · `GET /usage`

**Tenants** (onboarding + key management)
- `POST /tenants/signup` · `POST /tenants` · `POST /tenants/{tenant_id}/status`
- `GET /tenant` · `GET/POST /tenant/keys` · `DELETE /tenant/keys/{keyId}`
- `PUT /tenant/provider-keys` (BYOK, KMS-sealed) · `PUT /tenant/webhooks`

**Spaces** — `POST /space`, the async Space's single op-dispatched endpoint. It is in `/v1` but **keyless**: it
authenticates by key possession, not a tenant key (see the note below).

**Infra** — `GET /turn` (ephemeral TURN credentials).

> **Not at `/v1`:** ratings, `/agents/*`, `/earnings` and `/sessions/{id}` are `x-status: legacy` — they exist on
> the older surface but are *not* routed here, and calling them returns 404. They appear in `openapi.full.json`
> labelled as such, never in the production spec.

## Access

Invite-only during private beta. Email **hello@witbitz.chat** for keys. Everything above is served openly as a
contract; the *keys* are what's gated.

## Notes

- The async **Spaces** runtime (`/space`) is a separate, content-blind surface (key possession, not tenant keys) —
  see [async Spaces](./async-spaces.md) and [the Space link is the auth](./room-link-auth.md).
- This page is a summary; the OpenAPI at `/v1/openapi.json` is the source of truth (schemas, per-op auth, examples).
