# MIDL Write API — manual (MID-77)

The authenticated, headless write API for managing MIDL pages (frames) over HTTP. Pairs with the
read/agent surface (MID-52: `/.well-known/agent.json`, `/api/agents/openapi.json`, the `midl+json`
alternate, the Facts API). The machine spec is **`GET /api/agents/openapi.json`** (OpenAPI 3.1).

## Authentication

All write methods require a **Bearer API key**:

```
Authorization: Bearer <key>
```

- **Mint a key** in the admin UI (`/admin/keys`) or `POST /api/admin/keys` (admin-gated). The plaintext
  key is shown **once** — store it; only its hash is kept server-side (MID-75).
- **Rotate** = revoke + mint. **Revoke** via `/admin/keys` or `DELETE /api/admin/keys/:id`.
- The bootstrap `ADMIN_TOKEN` also authorizes (header `x-admin-token`, Bearer, or `?token=`).
- Reads (`GET /api/frames`, `/render/<slug>.midl.json`, `POST /api/validate`) are **public**.

## Endpoints

### `POST /api/frames` — create
Body: `{ content: <MIDL doc>, slug?, description?, is_active? }`. The `content` is validated
(`validateStudioMIDL`) **before** persisting. → `201 { id, slug }`.

```bash
curl -X POST https://<site>/api/frames \
  -H "Authorization: Bearer $MIDL_KEY" -H 'Content-Type: application/json' \
  -d '{"slug":"pricing","content":{"midl":"0.1","ns":{"ui":"https://midl.ai/ui#"},"frames":[{"id":"p","type":"ui:Page","children":[]}]}}'
```

### `PATCH /api/frames/:id` — update
Body: any of `{ content?, slug?, description?, is_active? }` (≥1 field). Supplied `content` is
re-validated. → `200`.

### `DELETE /api/frames/:id` — delete
→ `200`.

## Slugs (MID-76)
Lowercase, url-safe (`a-z`, `0-9`, hyphens), **not** a reserved word (`home`, `admin`, `api`, `render`,
`login`, …), and **unique**. Omit `slug` to get a generated `page-<ts>` default.

## Errors (sanitized — no raw DB text)
| Status | Meaning |
|---|---|
| 400 | Malformed body, or invalid/reserved slug |
| 401 | Missing/invalid API key |
| 404 | Frame id not found (PATCH/DELETE) |
| 409 | Slug already in use |
| 422 | Invalid MIDL document — not persisted |

## Notes
- Per-tenant scoping (a key → its own pages) is **P2** (MID-78/79); today a valid key is full-admin.
- Migration `010_api_keys.sql` must be applied to the target D1 before keys work there.
