For Florida law firms
Platform API v1 — scoped keys, honest pages and proposals (/api/v1)
firm lane · Platform service
Current availability
ShippedConfigured and enabled: firm admins mint scoped keys; the API reads, and its one write files a proposal the firm decides.
- Where it lives
- /firms/manage/api-keys · /docs/api
- What unlocks it
- a firm admin's key
Status is evaluated against this deployment's configuration by the capability-status service at build time; the catalogue's facts were last reviewed on the date shown.
Capabilities
- ShippedKeys a firm admin mints with what each may do (eight scopes), whose view it reads (attorney, or client-safe — never a work-product scope), which matters (the whole firm, or an allowlist that only narrows), an expiry within two years, rotation (a proposal grant never carried) and an owner (revoked when its admin leaves unless approved as a named service's); shown once, SHA-256 at rest.
- ShippedReads: matters, one matter (richer for an allowlisted key; document fingerprints with documents:meta), deadlines with their rule and citation, receipts, the agenda and the capability catalogue — ids, states, dates and hashes, never a name or a document's contents; never a matter with a wall.
- ShippedThe honest envelope on every answer (schema version, as-of, the verified-read triple) and pagination that reads one row past the page — has_more is never a guess; the cursor is a position only.
- ShippedOne write: a proposal on a matter for the firm's attorneys to decide (record:propose) — nothing executed, anything beyond a title, a description and requested data stripped and named; once per Idempotency-Key.
- ShippedPer-key rate lanes stated on every answer (RateLimit-* headers, enforced globally), and the OpenAPI 3.1 document at /api/v1/openapi.json.
Limits
- Pages of at most 200 rows; 120 reads and 30 writes a minute per key.
- Three undecided proposals per matter; a proposal is never applied by the platform — the firm decides it.
What EstateDraftFL refuses
| Reason code | HTTP | What it means |
|---|---|---|
| invalid_key | 401 | Missing, unknown, revoked or expired — the same answer for each. |
| insufficient_scope | 403 | The key does not hold the scope named, or its sensitivity may never hold it. |
| not_found | 404 | The same answer for an unknown id and one the key may not read. |
| invalid | 400 | A parameter, the cursor or the body could not be read — the field is named. |
| idempotency_key_reused | 409 | The key was used with a different request. |
| too_many_open | 409 | Three proposals already wait on the matter. |
| rate_limited | 429 | The key's lane is spent; Retry-After says when it refills. |
| unavailable | 503 | The read or write could not be completed; retry — never an empty list (matters: matters_read_unavailable). |
Evidence
- src/lib/api-keys.test.ts
- src/app/api/v1/matters/route.test.ts
- src/lib/platform/platform.test.ts
- src/lib/platform/operations.test.ts
- supabase/migrations/20261002000000_phase14_keys_v2.sql
- supabase/migrations/20261002020000_phase14_proposals.sql
Last reviewed 2026-10-02