# Charming OpenAPI 3.1

This is the markdown twin of the Charming HTTP contract. The machine-readable spec lives at <https://charm.ing/.well-known/openapi.json> — fetch it for the full OpenAPI 3.1 document with every route, request/response schema, error `kind` enum, and security scheme. This page is a human-readable orientation: what's in there, grouped by purpose. The narrative build manual that walks the same surface end-to-end is [llms-full.txt](https://usecharming.com/llms-full.txt).

## Surface

`https://charm.ing` exposes a single Bearer-secured HTTP API. Three security schemes (`appToken`, `userToken`, `renderToken`, plus `renderTokenQuery` for `?t=`) authorise three scopes. See [auth.md](https://usecharming.com/auth.md) for the token families and the device-pairing flow, and [pricing.md](https://usecharming.com/pricing.md) for the per-route limits and TTLs.

## Route groups

The spec organises around the lifecycle of a Charming app.

**App lifecycle.** `POST /app` creates (anonymous or authenticated upsert), `GET /app` lists the authenticated user's apps, `PUT /app/{id}` updates, `DELETE /app/{id}` deletes, `POST /app/{id}/claim` transfers ownership of an anonymous app to a signed-in user.

**App source & metadata.** `GET /app/{id}/source` reads the exact persisted `module` / `ui` / `styles` plus a strong `ETag: "v<N>"` for optimistic concurrency, `PATCH /app/{id}/source` applies exact-string find/replace edits gated by `If-Match`, `GET /app/{id}/describe` returns the owner-only metadata + storage key inventory, `GET /app/{id}/openapi.json` returns the per-app OpenAPI document derived from `export const routes`, `PUT /app/{id}/remixable` flips visitor auto-fork on/off, `PUT /app/{id}/public` flips no-login public access on/off (anyone with the URL reads+writes the app's shared data), `PUT /app/{id}/starter-prompt` sets the chat-with-app starter prompt.

**App runtime I/O.** `POST /app/{id}/api/{operation}` is the external entry point into an app's `fetch` handler — the platform strips `/app/<id>` so the app sees `/api/<op>`. `GET /app/{id}/events` is the live-state SSE stream (`state-changed` frames, with a `reconnect-resync` synthetic event on stale `Last-Event-ID`). `GET /app/{id}/activity` reads durable runtime-failure events. `GET /app/{id}/diag` and `POST /app/{id}/diag/report` cover the in-memory ring buffer plus its anonymous ingest. `POST /app/{id}/load-error/report` records bridge-caught load failures.

**Assets & secrets.** `POST /app/{id}/assets` uploads (10 MiB/asset, 50 assets/app, 100 MiB/app), `GET /app/{id}/assets` lists, `DELETE /app/{id}/assets/{key}` removes. `GET/POST/PUT/DELETE /app/{id}/secrets` manages write-only encrypted secrets (8 KiB cap, name `^[A-Z][A-Z0-9_]*$`) referenced from app source via `{{secret:NAME}}`. POST is create-only (returns 409 `secret_already_exists` for a duplicate name); use PUT to replace an existing value. `POST /app/{id}/feedback` submits durable feedback (text 16 KiB, structuredData 64 KiB serialized, wire 96 KiB).

**Resolution.** `GET /app/resolve/{manifestId}` resolves a manifestId to an appId in the caller's scope.

**Authentication.** `POST /api/token` mints a `bld_user_*` (7-day TTL), `GET /api/token` lists, `DELETE /api/token/{id}` revokes. `POST /api/pair/start` mints an RFC 8628-shaped `device_code`, `POST /api/pair/poll` polls for the paired `bld_user_*` (30-day TTL), `POST /api/pair/approve` is the browser-side approval. The legacy `/token` and `/pair/*` paths 308-redirect to the `/api/*` form.

**Discovery.** `GET /.well-known/openapi.json` returns this document. `GET /.well-known/oauth-authorization-server` and `GET /.well-known/oauth-protected-resource` carry the RFC 8414 / RFC 9728 metadata for OAuth-aware clients — the authorization-server document also splices an `agent_auth` extension block that points an agent at pairing, claim, revocation, and this skill file. `GET /.well-known/http-message-signatures-directory` serves the RFC 9421 / Web Bot Auth signing-key JWK Set.

**Prompts.** `GET /api/prompts/buildy-app-guide` and `GET /api/prompts/buildy-design-guide` serve the canonical authoring and design guides as Markdown — the same prose the MCP `buildy:app-guide` / `buildy:design-guide` prompts return.

## Error model

Every non-2xx response carries `{ ok: false, error: { kind, message, ... } }`. The `kind` field is the stable enum-shaped key — branch on it for recovery flows. Notable kinds: `unauthorized` (with `reason` and, for the post-claim case, a self-contained `recovery` block — see [auth.md](https://usecharming.com/auth.md#errors)), `not_found`, `already_claimed`, `duplicate_remix`, `manifest_id_immutable`, `payload_too_large`, `module_too_large`, `ui_too_large`, `text_too_large`, `structured_data_too_large`, `asset_too_large`, `asset_count_exceeded`, `asset_quota_exceeded`, `rate_limited`, `version_mismatch`, `precondition_required`, `invalid_if_match`, `edits_too_large`, `old_string_not_found`, `old_string_not_unique`. The full enum is in the `Error` schema in `components.schemas`.

## Rate-limit response headers

The probed ingest routes (diag/feedback/load-error/asset writes under **App runtime I/O** + **Assets & secrets**, and pair start/poll/approve under **Authentication**) emit `RateLimit-Limit` / `RateLimit-Remaining` / `RateLimit-Reset` headers on both the success and 429 responses, plus their legacy `X-RateLimit-*` aliases. On a 429 they also emit `Retry-After` (integer seconds, equal to `RateLimit-Reset`). The reusable header components live under `components.headers` in the spec and are referenced from every probed route's 200/204/201 and 429 responses, so generated clients can pick them up automatically. See [pricing.md](https://usecharming.com/pricing.md#rate-limits) for the per-route caps.

## Timeout SLAs

`info["x-buildy-sla"]` carries `read_ms`, `mutate_ms`, and `call_ms` — the family targets the server tries to hit for GET reads, mutations, and `POST /app/{id}/api/{operation}` respectively. Operations whose target deviates from the family default override it with `x-buildy-timeout-ms` on the operation object (e.g. `POST /app/{id}/assets` is 20000; `GET /app/{id}/events` is `null` for the long-lived SSE stream and carries an `x-buildy-timeout-note`). These are documentation only — the disclaimer in `info.description` is load-bearing — so codegen consumers should treat them as defaults for client read timeouts, not as enforced server ceilings. See [build-http.md](https://usecharming.com/build-http.md#timeout-slas) and [llms-full.txt](https://usecharming.com/llms-full.txt) for the narrative.

## How to consume

- **As an HTTP client.** Fetch <https://charm.ing/.well-known/openapi.json> and feed it to your OpenAPI-aware generator (oapi-codegen, openapi-typescript, openapi-generator, etc.). Charming hand-curates the document from `apps/server/src/openapi/spec.ts`, so route descriptions are written for agent consumption, not just humans.
- **As an MCP client.** The same operations are exposed as MCP tools (`create_app`, `update_app`, `query_app`, `mutate_app`, `submit_feedback`, …) at `https://charm.ing/mcp`. See [build-mcp.md](https://usecharming.com/build-mcp.md) and [clients.txt](https://usecharming.com/clients.txt) for the per-host setup paste-strings.
- **For a quick narrative.** Read [build-http.md](https://usecharming.com/build-http.md) (HTTP path), [build-mcp.md](https://usecharming.com/build-mcp.md) (MCP path), or [llms-full.txt](https://usecharming.com/llms-full.txt) (full manual including capability contracts, the manifest, and the result envelope).

The spec is regenerated on every server boot (`buildSpec(APP_BASE_URL)` in `apps/server/src/openapi/spec.ts`) and the constants it references — `MODULE_MAX_BYTES`, `LIST_APPS_DEFAULT_LIMIT`, `ANON_APP_TTL_MS`, `TOKEN_TTL_MS`, `PAIR_TTL_MS`, `PAIRED_TOKEN_TTL_MS`, `FEEDBACK_TEXT_MAX_LEN`, `FEEDBACK_STRUCTURED_DATA_MAX_LEN`, `ASSET_*_MAX` — are the same constants the route handlers enforce. If the live spec disagrees with this page, trust the spec.
