{"openapi":"3.1.0","info":{"title":"Memoshelf","version":"3.121.45","summary":"Memoshelf: where your AI agents write things down","description":"Your agents forget everything between runs. Memoshelf is where they write it down: memory, a run log you can read, and page reading. One key. Hashed API keys (`Authorization: Bearer ms_…` or `X-Api-Key`). Errors for 400/401/404/413/429 are `{error, code, fix}`. THE read call is GET /v1/what_happened_last?lane=<name>. Email alerts are not sent yet. Set alert_webhook_url to receive alerts today; alert_email is saved for when email alerts launch. Billing routes return 404 until paid plans open. This document contains no API secrets."},"servers":[{"url":"/","description":"This host"}],"tags":[{"name":"health","description":"Liveness. No auth. No secrets."},{"name":"runs","description":"Public list GET is unchanged (synthetic seed without a key). Owner list items and GET /v1/runs/{id} need a hashed key. Writes, patches, and deletes need a hashed key. Seed samples 404 on owner GET by id. Runs v2 adds status, started_at, finished_at, duration_ms, tags, small summary JSON, and optional expected_by. List filters: status, tag, since, until, q, stale=1."},{"name":"owner","description":"Per-owner settings. GET reports retention_days from the workspace plan (free 30 / solo 90 / team 365) and optional digest webhook URL. The webhook secret is write-only and never returned. Hashed key required."},{"name":"notes","description":"Public list GET is unchanged (synthetic seed without a key). Owner list items and GET /v1/notes/{id} need a hashed key. Writes, patches, and deletes need a hashed key. Seed samples 404 on owner GET by id. Notes v2 adds optional title, 32KB body cap, list filters (tag, since, until), tsvector search on Supabase (substring on fallback), and cron purge with the same retention_days as runs. Unacked system notes (operator memoshelf) sort first, then pinned notes. POST /v1/notes/{id}/ack acknowledges a system note. An API key cannot write operator memoshelf or kind system."},{"name":"md","description":"Public http(s) only. Honors robots.txt. 429 responses include rate-limit headers."},{"name":"keys","description":"Mint shows the `ms_…` secret once. Only SHA-256 is stored. Revoke marks revoked_at; that secret then 401s."},{"name":"signup","description":"POST /v1/signup {email} creates one free workspace and key. Sign-up may be closed. GET /pricing.json serves the plans table."},{"name":"webhooks","description":"Per-owner outbound webhooks for run.finished and note.stored. HMAC-SHA256 in x-memoshelf-signature. Signing secret shown once on create."},{"name":"handoff","description":"Keyed session handoff. GET /v1/what_happened_last is a read scoped to the caller's workspace: last N runs with their summaries plus recent notes (pinned first) as ≤300 tokens of markdown. `notes` is a structured array (id, title, body, tags, pinned, created_at) beside `runs`. notes_omitted counts matching notes that array leaves out. Optional project and routine filter by tag or goal/title. Seeds stay out."},{"name":"export","description":"Owner runs, notes, key metadata, and usage. Immediate JSON at GET /v1/export; CSV at /v1/export.csv. POST /v1/export queues a JSON + markdown zip served at GET /v1/export/{id} for 7 days. DELETE /v1/workspace soft-deletes with a 30-day cancel (POST /v1/workspace). Both write an audit_events row (who, key, action, target, request id)."},{"name":"billing","description":"Billing routes return 404 until paid plans open."},{"name":"usage","description":"Workspace usage_daily rollups (runs, notes, fetches, fetch_bytes, tokens_out) plus per-key daily counters. GET /v1/usage returns this UTC month and last 30 days, plus per-key last_request_at and quiet_for_hours, plus included quantities. Each per-key day may include routes (a route-to-count map; additive) and outcome counters status_2xx, status_4xx, status_5xx plus fail_routes (most common failing route+status). MCP usage_summary matches. PATCH /v1/owner quotas. 429 quota_exceeded includes retry_after, reset_at and a plain one-sentence fix. A read is any successful agent call that returns notes or runs (GET /v1/notes, /v1/notes/{id}, /v1/runs*, /v1/what_happened_last, /v1/lanes/{lane}/notes and the MCP equivalents); owner-app browsing, /health, /v1/usage and failed calls never count. The response adds ais (active keys plus active Connect grants, with over_limit as a notice, never a cutoff) and limits (used, limit, percent and reset_at for AIs, notes, reads, runs and page fetches). /health consumerHealth is the last 24 hours of those outcomes (requests, errorRate, worstRoute, notFoundReasons) — never a key, id, or body."},{"name":"mcp","description":"Streamable HTTP JSON-RPC at POST /mcp. Config snippets are for common MCP clients. Every POST requires a hashed key or Connect token. Unauthenticated POST or GET /mcp is 401."}],"paths":{"/health":{"get":{"tags":["health"],"summary":"Liveness","description":"No auth. Returns `{ok, version, product, env, dataSource, degraded}` plus whether scheduled jobs are on time. No secrets.","operationId":"health","security":[],"responses":{"200":{"description":"Ok","headers":{"x-request-id":{"description":"Request id echoed from a safe incoming X-Request-Id, or a minted UUID.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/v1/health":{"get":{"tags":["health"],"summary":"Liveness (same body as /health)","description":"Same public body as GET /health. No auth. Never a secret.","operationId":"healthV1","security":[],"responses":{"200":{"description":"Ok","headers":{"x-request-id":{"description":"Request id echoed from a safe incoming X-Request-Id, or a minted UUID.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/v1/runs":{"get":{"tags":["runs"],"summary":"List runs","description":"Public seed GET is unchanged: no key and empty/omitted `q` returns fictional seed samples. A hashed key is required for owner items. `q` searches owner goal/outcome/title/body only. Non-empty `q` without a key → 401. `limit` default 50, max 100. `cursor` is the last id from the previous page. Optional `status`, `tag`, `since`, `until`, and `stale=1` filter the page (invalid values → 400). Optional max_chars (default 4000) stops the page when serialized items would exceed the budget. Accepted query parameters: limit, cursor, q, max_chars, status, tag, tags, lane, since, until, stale. Unknown keys return warnings, not 400. Keyed lists read from the database (not isolate memory): newest first, a total count of matching rows, and cursor paging that drops nothing. Responses include truncated (true when more items remain), next_cursor, and total.","operationId":"listRuns","security":[{},{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"Page size. Default 50, max 100.","schema":{"type":"integer","default":50,"minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"Id of the last item from the previous page. Omit on the first page.","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Owner search. Case-insensitive substring match on goal, outcome, title, or body. Hashed key required. Empty or omitted lists as before (public seed without a key; owner items with a key). Non-empty q without a key → 401. Matches owner items only; seed samples are never mixed in.","schema":{"type":"string"}},{"name":"max_chars","in":"query","required":false,"description":"Character budget for this read. Default 4000, max 80000. Responses include truncated and a continuation next_cursor.","schema":{"type":"integer","default":4000,"minimum":1,"maximum":80000}},{"name":"status","in":"query","required":false,"description":"Filter runs by status: started, succeeded, or failed.","schema":{"type":"string","enum":["started","succeeded","failed"]}},{"name":"tag","in":"query","required":false,"description":"Filter runs that include this tag.","schema":{"type":"string"}},{"name":"tags","in":"query","required":false,"description":"Comma-separated tags. All must match, case-insensitive. THE read call is GET /v1/what_happened_last?lane=<name> (alias for tags=lane:<name>). Unknown query keys return warnings, not 400.","schema":{"type":"string"}},{"name":"lane","in":"query","required":false,"description":"Alias for tags=lane:<name>. Same result as tags=lane:<name>.","schema":{"type":"string"}},{"name":"since","in":"query","required":false,"description":"Inclusive lower bound on started_at (ISO datetime or YYYY-MM-DD).","schema":{"type":"string"}},{"name":"until","in":"query","required":false,"description":"Inclusive upper bound on started_at (ISO datetime or YYYY-MM-DD).","schema":{"type":"string"}},{"name":"stale","in":"query","required":false,"description":"When 1 or true, only started-but-unfinished runs whose expected_by is in the past. Runs without expected_by are omitted. Invalid values → 400 invalid_stale.","schema":{"type":"string","enum":["1","0","true","false"]}}],"responses":{"200":{"description":"Run page","content":{"application/json":{"schema":{"type":"object","required":["runs","next_cursor","truncated","total"],"properties":{"runs":{"type":"array","items":{"$ref":"#/components/schemas/Run"}},"next_cursor":{"type":["string","null"]},"truncated":{"type":"boolean"},"total":{"type":"integer","minimum":0}}}}}},"400":{"description":"invalid_status, invalid_since, invalid_until, invalid_stale, invalid_max_chars, or unknown_param","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Presented key is unknown, or non-empty q without a key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["runs"],"summary":"Append a run","description":"Hashed owner key required (401 without). Owner /app create form posts {goal, tools, outcome} here the same way other owner writes do. MCP create_run wraps this same keyed POST. Created records persist in gitignored `.data/` and then work with GET-by-id, PATCH, delete, CSV, MCP get_run, and ?q=. Seed samples cannot be created as seed ids. A lane-bounded key auto-tags lane:<its lane> and returns 403 lane_forbidden for any other lane tag; lane:team is writable only by unrestricted keys. Idempotency-Key contract: same key plus same body replays (200); same key plus a different body is 409 idempotency_key_reuse with the existing record's id and created-at; keys are caller-scoped and never reused across logical operations.","operationId":"createRun","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Caller-scoped Idempotency-Key. Same key plus same body within 10 minutes replays the original record (200). Same key plus a different body is 409 idempotency_key_reuse with that record's id and created-at. Keys are never reused across logical operations (fingerprint is key id + runs|notes scope + header).","schema":{"type":"string","maxLength":256}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"goal":{"type":"string"},"outcome":{"type":"string"},"tools":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["started","succeeded","failed"]},"started_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"finished_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"duration_ms":{"type":"integer","minimum":0,"maximum":9007199254740991},"tags":{"type":"array","items":{"type":"string"}},"summary":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"expected_by":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"process":{"description":"Optional claim, e.g. {read_first: true}.","type":"object","properties":{"read_first":{"type":"boolean"}},"additionalProperties":false}},"required":["goal","outcome"],"additionalProperties":false,"description":"JSON body max KB. Over the cap → 413 payload_too_large. Default status is succeeded."}}}},"responses":{"200":{"description":"Idempotent replay of the original record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Run"}}}},"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Run"}}}},"400":{"description":"goal and outcome required, or invalid Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"idempotency_key_reuse: same Idempotency-Key plus a different body. Body includes id and created_at of the record the key already points at.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"JSON body over KB (runs/notes) or md URL string over KB. MCP tool args use the same caps.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/runs/{id}":{"get":{"tags":["runs"],"summary":"Get one owner run","description":"Hashed owner key required. Returns one persisted owner run plus truncated and next_cursor. Optional max_chars (default 4000) slices goal then outcome. cursor is field:offset or a concat offset. Accepted query parameters: max_chars, cursor. Unknown keys return warnings, not 400. Seed samples and missing ids return 404. Public list GET is unchanged.","operationId":"getRun","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"max_chars","in":"query","required":false,"description":"Character budget for this read. Default 4000, max 80000. Responses include truncated and a continuation next_cursor.","schema":{"type":"integer","default":4000,"minimum":1,"maximum":80000}},{"name":"cursor","in":"query","required":false,"description":"Continuation cursor from the previous page (`field:offset` or a concat offset).","schema":{"type":"string"}}],"responses":{"200":{"description":"Owner run with char-budget fields","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Run"},{"type":"object","required":["truncated","next_cursor"],"properties":{"truncated":{"type":"boolean"},"next_cursor":{"type":["string","null"]}}}]}}}},"400":{"description":"invalid_max_chars, invalid_cursor, or unknown_param","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Missing or seed sample","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["runs"],"summary":"Patch an owner run","description":"Hashed owner key required. Partial update of goal, tools, outcome, status, timing, tags, and/or summary. Seed samples cannot be edited and return 404. Change persists in gitignored `.data`. JSON body max KB. MCP update_run wraps this same keyed PATCH.","operationId":"updateRun","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"goal":{"type":"string"},"outcome":{"type":"string"},"tools":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["started","succeeded","failed"]},"started_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"finished_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"duration_ms":{"type":"integer","minimum":0,"maximum":9007199254740991},"tags":{"type":"array","items":{"type":"string"}},"summary":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"expected_by":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"}},"additionalProperties":false,"description":"At least one field. Empty goal/outcome → 400. Over KB → 413."}}}},"responses":{"200":{"description":"Updated owner run","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Run"}}}},"400":{"description":"nothing_to_patch, invalid_goal, invalid_outcome, or invalid_tools","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Missing or seed sample","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"JSON body over KB (runs/notes) or md URL string over KB. MCP tool args use the same caps.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["runs"],"summary":"Delete an owner run","description":"Hashed owner key required (401 without). Owner /app detail Delete control calls this the same way other owner writes do. Seed samples cannot be deleted and return 404. Removal persists in gitignored `.data`. After delete, GET-by-id / PATCH / MCP get_run / MCP delete_run return 404 and the id is gone from ?q= / CSV / owner list.","operationId":"deleteRun","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","required":["deleted"],"properties":{"deleted":{"type":"string"}}}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Missing or seed sample","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/notes":{"get":{"tags":["notes"],"summary":"List notes","description":"Public seed GET is unchanged: no key and empty/omitted `q` returns fictional seed samples. A hashed key is required for owner items. `q` searches owner title/body (substring on file/memory; tsvector token-AND on Supabase). Non-empty `q` without a key → 401. `limit` default 50, max 100. `cursor` is the last id from the previous page. Optional `tag`, `tags` (comma-separated; all must match, case-insensitive), `since`, and `until` filter the page (invalid values → 400). Optional max_chars (default 4000) stops the page when serialized items would exceed the budget. Accepted query parameters: limit, cursor, q, max_chars, tag, tags, lane, since, until. Unknown keys return warnings, not 400. Keyed lists read from the database (not isolate memory): newest first, a total count of matching rows, and cursor paging that drops nothing. Responses include truncated, next_cursor, and total.","operationId":"listNotes","security":[{},{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"Page size. Default 50, max 100.","schema":{"type":"integer","default":50,"minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"Id of the last item from the previous page. Omit on the first page.","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Owner search. Case-insensitive substring match on goal, outcome, title, or body. Hashed key required. Empty or omitted lists as before (public seed without a key; owner items with a key). Non-empty q without a key → 401. Matches owner items only; seed samples are never mixed in.","schema":{"type":"string"}},{"name":"max_chars","in":"query","required":false,"description":"Character budget for this read. Default 4000, max 80000. Responses include truncated and a continuation next_cursor.","schema":{"type":"integer","default":4000,"minimum":1,"maximum":80000}},{"name":"tag","in":"query","required":false,"description":"Filter runs that include this tag.","schema":{"type":"string"}},{"name":"tags","in":"query","required":false,"description":"Comma-separated tags. All must match, case-insensitive. THE read call is GET /v1/what_happened_last?lane=<name> (alias for tags=lane:<name>). Unknown query keys return warnings, not 400.","schema":{"type":"string"}},{"name":"lane","in":"query","required":false,"description":"Alias for tags=lane:<name>. Same result as tags=lane:<name>.","schema":{"type":"string"}},{"name":"since","in":"query","required":false,"description":"Inclusive lower bound on started_at (ISO datetime or YYYY-MM-DD).","schema":{"type":"string"}},{"name":"until","in":"query","required":false,"description":"Inclusive upper bound on started_at (ISO datetime or YYYY-MM-DD).","schema":{"type":"string"}}],"responses":{"200":{"description":"Note page","content":{"application/json":{"schema":{"type":"object","required":["notes","next_cursor","truncated","total"],"properties":{"notes":{"type":"array","items":{"$ref":"#/components/schemas/Note"}},"next_cursor":{"type":["string","null"]},"truncated":{"type":"boolean"},"total":{"type":"integer","minimum":0}}}}}},"400":{"description":"invalid_since, invalid_until, invalid_tags, invalid_lane, or invalid_max_chars","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Presented key is unknown, or non-empty q without a key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["notes"],"summary":"Store a note","description":"Hashed owner key required (401 without). Owner /app create form posts {body, optional title, tags} here. MCP create_note wraps this same keyed POST. Body max 32KB (body_too_large). Created records persist in gitignored `.data/` and then work with GET-by-id, PATCH, delete, CSV, MCP get_note, and ?q=. Seed samples cannot be created as seed ids. pinned pages are kept past the plan's retention window; unpin to let one expire. A lane-bounded key auto-tags lane:<its lane> and returns 403 lane_forbidden for any other lane tag; lane:team is writable only by unrestricted keys. Idempotency-Key contract: same key plus same body replays (200); same key plus a different body is 409 idempotency_key_reuse with the existing record's id and created-at; keys are caller-scoped and never reused across logical operations.","operationId":"createNote","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Caller-scoped Idempotency-Key. Same key plus same body within 10 minutes replays the original record (200). Same key plus a different body is 409 idempotency_key_reuse with that record's id and created-at. Keys are never reused across logical operations (fingerprint is key id + runs|notes scope + header).","schema":{"type":"string","maxLength":256}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":200},"body":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"pinned":{"type":"boolean"},"process":{"description":"Optional claim, e.g. {read_first: true}.","type":"object","properties":{"read_first":{"type":"boolean"}},"additionalProperties":false}},"required":["body"],"additionalProperties":false,"description":"JSON envelope max KB. Note body max 32768 bytes. Over the envelope → 413 payload_too_large."}}}},"responses":{"200":{"description":"Idempotent replay of the original record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}}},"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}}},"400":{"description":"body_required, body_too_large, invalid_title, invalid_tags, invalid_pinned, or invalid Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"idempotency_key_reuse: same Idempotency-Key plus a different body. Body includes id and created_at of the record the key already points at.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"JSON body over KB (runs/notes) or md URL string over KB. MCP tool args use the same caps.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/notes/{id}":{"get":{"tags":["notes"],"summary":"Get one owner note","description":"Hashed owner key required. Returns one persisted owner note plus truncated and next_cursor. Optional max_chars (default 4000) slices body. cursor is field:offset or a concat offset. Accepted query parameters: max_chars, cursor. Unknown keys return warnings, not 400. Seed samples and missing ids return 404. Public list GET is unchanged.","operationId":"getNote","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"max_chars","in":"query","required":false,"description":"Character budget for this read. Default 4000, max 80000. Responses include truncated and a continuation next_cursor.","schema":{"type":"integer","default":4000,"minimum":1,"maximum":80000}},{"name":"cursor","in":"query","required":false,"description":"Continuation cursor from the previous page (`field:offset` or a concat offset).","schema":{"type":"string"}}],"responses":{"200":{"description":"Owner note with char-budget fields","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Note"},{"type":"object","required":["truncated","next_cursor"],"properties":{"truncated":{"type":"boolean"},"next_cursor":{"type":["string","null"]}}}]}}}},"400":{"description":"invalid_max_chars, invalid_cursor, or unknown_param","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Missing or seed sample","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["notes"],"summary":"Patch an owner note","description":"Hashed owner key required. Partial update of title, body, tags, and/or pinned. Seed samples cannot be edited and return 404. Change persists in gitignored `.data`. JSON envelope max KB. Note body max 32KB. MCP update_note wraps this same keyed PATCH.","operationId":"updateNote","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":200},"body":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"pinned":{"type":"boolean"}},"additionalProperties":false,"description":"At least one field. Empty body → 400. Body over 32KB → 400 body_too_large. Envelope over KB → 413."}}}},"responses":{"200":{"description":"Updated owner note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}}},"400":{"description":"nothing_to_patch, invalid_body, invalid_title, body_too_large, invalid_tags, or invalid_pinned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Missing or seed sample","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"JSON body over KB (runs/notes) or md URL string over KB. MCP tool args use the same caps.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["notes"],"summary":"Delete an owner note","description":"Hashed owner key required (401 without). Owner /app detail Delete control calls this the same way other owner writes do. Seed samples cannot be deleted and return 404. Removal persists in gitignored `.data`. After delete, GET-by-id / PATCH / MCP get_note / MCP delete_note return 404 and the id is gone from ?q= / CSV / owner list.","operationId":"deleteNote","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","required":["deleted"],"properties":{"deleted":{"type":"string"}}}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Missing or seed sample","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/notes/{id}/ack":{"post":{"tags":["notes"],"summary":"Acknowledge a system note","description":"Write-scoped API key required. Marks a system note in the caller's workspace as acked. Already-acked notes replay. User notes and missing ids return 404. MCP notes action ack is the same.","operationId":"ackSystemNote","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The acked system note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Missing, seed sample, or not a system note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/lanes/{lane}/notes":{"get":{"tags":["notes"],"summary":"List notes on one lane","description":"Keyed read of notes tagged lane:<lane> (plus team notes a bounded key can already see). A lane-bounded key can only call this for its own lane; other lanes return 403 lane_forbidden. Same query as GET /v1/notes (limit, cursor, q, max_chars, tag, tags, since, until). The URL lane wins over ?lane=. A successful call updates the key's last-read timestamp.","operationId":"listLaneNotes","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"lane","in":"path","required":true,"schema":{"type":"string"},"description":"Lane slug (lowercase). The list is filtered to lane:<lane>."},{"name":"limit","in":"query","required":false,"description":"Page size. Default 50, max 100.","schema":{"type":"integer","default":50,"minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"Id of the last item from the previous page. Omit on the first page.","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Owner search. Case-insensitive substring match on goal, outcome, title, or body. Hashed key required. Empty or omitted lists as before (public seed without a key; owner items with a key). Non-empty q without a key → 401. Matches owner items only; seed samples are never mixed in.","schema":{"type":"string"}},{"name":"max_chars","in":"query","required":false,"description":"Character budget for this read. Default 4000, max 80000. Responses include truncated and a continuation next_cursor.","schema":{"type":"integer","default":4000,"minimum":1,"maximum":80000}},{"name":"tag","in":"query","required":false,"description":"Filter runs that include this tag.","schema":{"type":"string"}},{"name":"tags","in":"query","required":false,"description":"Comma-separated tags. All must match, case-insensitive. THE read call is GET /v1/what_happened_last?lane=<name> (alias for tags=lane:<name>). Unknown query keys return warnings, not 400.","schema":{"type":"string"}},{"name":"since","in":"query","required":false,"description":"Inclusive lower bound on started_at (ISO datetime or YYYY-MM-DD).","schema":{"type":"string"}},{"name":"until","in":"query","required":false,"description":"Inclusive upper bound on started_at (ISO datetime or YYYY-MM-DD).","schema":{"type":"string"}}],"responses":{"200":{"description":"Note page","content":{"application/json":{"schema":{"type":"object","required":["notes","next_cursor","truncated","total"],"properties":{"notes":{"type":"array","items":{"$ref":"#/components/schemas/Note"}},"next_cursor":{"type":["string","null"]},"truncated":{"type":"boolean"},"total":{"type":"integer","minimum":0}}}}}},"400":{"description":"invalid_lane or the same codes as GET /v1/notes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"lane_forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["notes"],"summary":"Store a note on one lane","description":"Bounded write: the URL names the lane. A lane-bounded key can only call this for its own lane. Tags gain lane:<lane> automatically. Other lane tags and lane:team (for a bounded key) return 403 lane_forbidden. Unrestricted keys may write any lane, including team. Same body as POST /v1/notes.","operationId":"createLaneNote","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"lane","in":"path","required":true,"schema":{"type":"string"},"description":"Lane slug (lowercase). The write is tagged lane:<lane>."},{"name":"Idempotency-Key","in":"header","required":false,"description":"Caller-scoped Idempotency-Key. Same key plus same body within 10 minutes replays the original record (200). Same key plus a different body is 409 idempotency_key_reuse with that record's id and created-at. Keys are never reused across logical operations (fingerprint is key id + runs|notes scope + header).","schema":{"type":"string","maxLength":256}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":200},"body":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"pinned":{"type":"boolean"},"process":{"description":"Optional claim, e.g. {read_first: true}.","type":"object","properties":{"read_first":{"type":"boolean"}},"additionalProperties":false}},"required":["body"],"additionalProperties":false,"description":"JSON envelope max KB. Note body max 32768 bytes. Over the envelope → 413 payload_too_large."}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}}},"400":{"description":"invalid_lane or the same codes as POST /v1/notes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"lane_forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/lanes/{lane}/runs":{"post":{"tags":["runs"],"summary":"Log a run on one lane","description":"Bounded write: the URL names the lane. A lane-bounded key can only call this for its own lane. Tags gain lane:<lane> automatically. Other lane tags and lane:team (for a bounded key) return 403 lane_forbidden. Unrestricted keys may write any lane, including team. Same body as POST /v1/runs.","operationId":"createLaneRun","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"lane","in":"path","required":true,"schema":{"type":"string"},"description":"Lane slug (lowercase). The write is tagged lane:<lane>."},{"name":"Idempotency-Key","in":"header","required":false,"description":"Caller-scoped Idempotency-Key. Same key plus same body within 10 minutes replays the original record (200). Same key plus a different body is 409 idempotency_key_reuse with that record's id and created-at. Keys are never reused across logical operations (fingerprint is key id + runs|notes scope + header).","schema":{"type":"string","maxLength":256}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"goal":{"type":"string"},"outcome":{"type":"string"},"tools":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["started","succeeded","failed"]},"started_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"finished_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"duration_ms":{"type":"integer","minimum":0,"maximum":9007199254740991},"tags":{"type":"array","items":{"type":"string"}},"summary":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"expected_by":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"process":{"description":"Optional claim, e.g. {read_first: true}.","type":"object","properties":{"read_first":{"type":"boolean"}},"additionalProperties":false}},"required":["goal","outcome"],"additionalProperties":false,"description":"JSON body max KB. Over the cap → 413 payload_too_large. Default status is succeeded."}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Run"}}}},"400":{"description":"invalid_lane or the same codes as POST /v1/runs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"lane_forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/md":{"post":{"tags":["md"],"summary":"Public URL → markdown","description":"Fetches public `http`/`https` only. Sends `Accept: text/markdown` first and uses the origin markdown when returned; otherwise falls back to the readability extractor (nav/footer/cookie/feedback strip, heading-hierarchy repair). Response includes `extraction` (origin | main | full), `tokens_in`, and `tokens_out` (whitespace tokens). Echoes `x-markdown-tokens` when the origin sent it. Refuses private/loopback/link-local addresses after DNS. Honors robots.txt. Owner-scoped TTL cache in fetch_cache. No headless Chrome. 429 responses include rate-limit headers. Error codes: unsupported_scheme, private_address, robots_disallowed, login_wall, too_large, fetch_failed, timeout.","operationId":"publicUrlToMarkdown","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","maxLength":2048,"description":"Public http(s) URL. Max KB. Over the cap → 413 payload_too_large."},"max_chars":{"description":"Character budget for markdown. Default 4000.","type":"integer","minimum":1,"maximum":80000},"cursor":{"description":"Character offset into markdown from a previous next_cursor.","type":"string"}},"required":["url"],"additionalProperties":false}}}},"responses":{"200":{"description":"Markdown","headers":{"X-RateLimit-Limit":{"description":"Max markdown fetches per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining markdown fetches in the current window.","schema":{"type":"integer"}},"x-markdown-tokens":{"description":"Echoed from the origin when present.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","required":["url","markdown","extraction","tokens_in","tokens_out","truncated","next_cursor"],"properties":{"url":{"type":"string"},"title":{"type":"string"},"markdown":{"type":"string"},"extraction":{"type":"string","enum":["origin","main","full"]},"tokens_in":{"type":"integer","minimum":0,"description":"Whitespace token count of the fetched body before extraction."},"tokens_out":{"type":"integer","minimum":0,"description":"Whitespace token count of the returned markdown (before max_chars)."},"x-markdown-tokens":{"type":"string","description":"Echo of the origin x-markdown-tokens header when present."},"truncated":{"type":"boolean"},"next_cursor":{"type":["string","null"]}}}}}},"400":{"description":"url_required, unsupported_scheme, private_address, login_wall, or other rejected target","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"robots_disallowed — robots.txt disallows the URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"URL envelope payload_too_large or response too_large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Includes X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After.","headers":{"X-RateLimit-Limit":{"description":"Max markdown fetches per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining markdown fetches in the current window.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before retrying a 429.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"fetch_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"504":{"description":"timeout","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/signup":{"post":{"tags":["signup"],"summary":"Create a free workspace and API key","description":"Unkeyed. Body {email, us_confirmation}. Creates one free workspace and a write key immediately. us_confirmation must be true (United States location and agreement to the Terms and Privacy Policy). Rate-limited per IP and email (3.30.1 guard). One free workspace per email. Sends a Supabase magic link to claim the workspace when Auth env is set; empty env still returns the key (magic_link_sent false). Off unless the deploy environment is production and the signup switch is absent → 403 signup_disabled. The plaintext key is shown once.","operationId":"signup","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"Normalized to lowercase. One free workspace per email."},"us_confirmation":{"type":"boolean","description":"Required. Confirms United States location and agreement to the Terms and Privacy Policy."}},"required":["email","us_confirmation"],"additionalProperties":false}}}},"responses":{"201":{"description":"Created. Copy the key now; it is not shown again.","content":{"application/json":{"schema":{"type":"object","properties":{"workspace_id":{"type":"string"},"plan":{"type":"string","const":"free"},"id":{"type":"string","description":"Key id. Hash only is stored."},"key":{"type":"string","description":"Plaintext secret, shown once. Shape `ms_…`. Never stored."},"prefix":{"type":"string"},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"label":{"type":"string"},"email":{"type":"string"},"magic_link_sent":{"type":"boolean"}},"required":["workspace_id","plan","id","key","prefix","created_at","email","magic_link_sent"],"additionalProperties":false,"description":"Created free workspace and key. Copy the key now."}}}},"400":{"description":"invalid_email, disposable_email, or us_confirmation_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"signup_disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"already_signed_up","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"signup_throttled. Includes Retry-After.","headers":{"Retry-After":{"description":"Seconds to wait before retrying a 429.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auth/magic-link":{"post":{"tags":["signup"],"summary":"Send a sign-in magic link","description":"Unkeyed. Body {email, us_confirmation?}. When signup is open, sends a create-user magic link (same as before). When signup is closed, only an unused unexpired invited email or an existing account gets a link; every other new email is 403 signup_disabled with the existing closed message. Invite redemption requires us_confirmation; existing owners are not blocked. No invite wording in the response body.","operationId":"sendSignInMagicLink","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"next":{"type":"string"},"us_confirmation":{"description":"Required when redeeming an invite. Existing owners are not blocked.","type":"boolean"}},"required":["email"],"additionalProperties":false}}}},"responses":{"200":{"description":"Link send attempted","content":{"application/json":{"schema":{"type":"object","properties":{"sent":{"type":"boolean"},"email":{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["sent","email"],"additionalProperties":false}}}},"400":{"description":"invalid_email or us_confirmation_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"signup_disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auth/verify-code":{"post":{"tags":["signup"],"summary":"Sign in with the email code","description":"Unkeyed. Body {email, code, next?}. Verifies the code from the sign-in email, sets the session cookie, and returns the same-origin path to continue to (the original /oauth/authorize URL when sign-in started there).","operationId":"verifySignInCode","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"code":{"type":"string","description":"The code from the sign-in email."},"next":{"type":"string"}},"required":["email","code"],"additionalProperties":false}}}},"responses":{"200":{"description":"Signed in","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"location":{"type":"string","description":"Same-origin path to continue to."}},"required":["ok","location"],"additionalProperties":false}}}},"400":{"description":"invalid_email or invalid_code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"invalid_code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/keys":{"post":{"tags":["keys"],"summary":"Mint an API key","description":"Returns the `ms_…` secret once Only a SHA-256 hash is stored. `keys` is the owner list re-read from the store after the mint (including revoked keys; never the plaintext secret). Mint is locked unless a signed-in owner or a matching bootstrap header ( matching ) or the local preview flag. When Auth is configured, `/app` requires sign-in (same-origin, not a CORS cookie).","operationId":"mintKey","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"label":{"type":"string"},"scope":{"type":"string","enum":["read","write"]},"process_mode":{"type":"string","enum":["off","warn"]},"read_window_minutes":{"type":"integer","minimum":1,"maximum":10080},"lane":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}]}},"additionalProperties":false}}}},"responses":{"200":{"description":"Minted. Copy the key now; it is not shown again. `keys` is the store-re-read list.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","description":"Plaintext secret, shown once. Shape `ms_…`. Never stored."},"prefix":{"type":"string","description":"Non-secret prefix for later display."},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"name":{"type":"string"},"label":{"type":"string"},"scope":{"type":"string","enum":["read","write"]},"lane":{"type":"string"},"keys":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"prefix":{"type":"string","description":"Non-secret prefix for later display."},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"label":{"type":"string"},"scope":{"type":"string","enum":["read","write"]},"last_used_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"last_read_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"last_write_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"skipped_read_count":{"type":"integer","minimum":0,"maximum":9007199254740991},"process_mode":{"type":"string","enum":["off","warn"]},"read_window_minutes":{"type":"integer","minimum":1,"maximum":10080},"quiet_alert_enabled":{"type":"boolean"},"quiet_alert_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"failed_calls_alert_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"failed_today":{"type":"integer","minimum":0,"maximum":9007199254740991},"last_fail_fix":{"type":"string","maxLength":280},"reads_last_7d":{"type":"integer","minimum":0,"maximum":9007199254740991},"usage_count":{"type":"integer","minimum":0,"maximum":9007199254740991},"revoked_at":{"description":"Set when the key is revoked. Omitted while live.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T"},"lane":{"description":"When set, this key is bounded to that lane.","type":"string","maxLength":64}},"required":["id","prefix","created_at"],"additionalProperties":false},"description":"Owner key list re-read from the store after this mint, including revoked keys. Never includes the plaintext secret."}},"required":["id","key","prefix","created_at","keys"],"additionalProperties":false}}}},"401":{"description":"Auth is configured and there is no owner session","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/oauth/tokens":{"get":{"tags":["keys"],"summary":"List chat-connector tokens","description":"Owner-session (or bootstrap / open-rack) list of OAuth access tokens minted for MCP chat clients. Each token is a Connect grant bound to the signed-in workspace and AI name. No API key is shown. No plaintext.","operationId":"listOauthTokens","responses":{"200":{"description":"Tokens for this owner, including revoked rows.","content":{"application/json":{"schema":{"type":"object","properties":{"tokens":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"prefix":{"type":"string","description":"Non-secret prefix of the access token."},"client_id":{"type":"string"},"client_name":{"type":"string"},"key_id":{"type":"string","description":"Internal grant id. Not an owner-visible API key."},"key_label":{"type":"string"},"scope":{"type":"string","enum":["read","write"],"description":"Write for Connect grants. The token stays on its lane."},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"expires_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"revoked_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"last_used_at":{"description":"Last used time of this grant.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T"},"lane":{"description":"AI name this grant is bounded to.","type":"string"}},"required":["id","prefix","client_id","key_id","scope","created_at","expires_at"],"additionalProperties":false}}},"required":["tokens"],"additionalProperties":false}}}},"401":{"description":"No owner session","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/oauth/tokens/{id}":{"delete":{"tags":["keys"],"summary":"Revoke a chat-connector token","description":"Revokes one OAuth access token (Connected AI grant). The next /mcp call with that token is 401. Same mint-lock rule as POST /v1/keys.","operationId":"revokeOauthToken","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Token id (`otok_…`)."}],"responses":{"200":{"description":"Revoked. `tokens` is the store-re-read list.","content":{"application/json":{"schema":{"type":"object","properties":{"revoked":{"type":"string"},"tokens":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"prefix":{"type":"string","description":"Non-secret prefix of the access token."},"client_id":{"type":"string"},"client_name":{"type":"string"},"key_id":{"type":"string","description":"Internal grant id. Not an owner-visible API key."},"key_label":{"type":"string"},"scope":{"type":"string","enum":["read","write"],"description":"Write for Connect grants. The token stays on its lane."},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"expires_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"revoked_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"last_used_at":{"description":"Last used time of this grant.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T"},"lane":{"description":"AI name this grant is bounded to.","type":"string"}},"required":["id","prefix","client_id","key_id","scope","created_at","expires_at"],"additionalProperties":false}}},"required":["revoked","tokens"],"additionalProperties":false}}}},"401":{"description":"No owner session","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown token for this owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/keys/{id}":{"patch":{"tags":["keys"],"summary":"Update process-compliance and quiet-notebook settings on a key","description":"Sets process_mode (off|warn, default warn) and optional read_window_minutes override (same mint-lock as revoke). quiet_alert_enabled (default true) is also settable by the same API key (F6 tier 1).","operationId":"patchKey","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Key id from mint (`key_…`)."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"process_mode":{"type":"string","enum":["off","warn"]},"read_window_minutes":{"anyOf":[{"type":"integer","minimum":1,"maximum":10080},{"type":"null"}]},"quiet_alert_enabled":{"type":"boolean"},"lane":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}]}},"additionalProperties":false}}}},"responses":{"200":{"description":"Updated. `keys` is the store-re-read list.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"No owner session","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["keys"],"summary":"Revoke an API key","description":"Marks one minted key revoked in the same gitignored `.data/` store. The hash stays on disk; `revoked_at` is set. Owner `/app` re-reads the list and shows the key as revoked with its revoked time. That secret immediately 401s on REST and MCP like a missing key. MCP revoke_key wraps this same store path (identify by id or prefix). Same mint-lock rule as POST /v1/keys: signed-in owner, a matching bootstrap header, or the local preview flag.","operationId":"revokeKey","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Key id from mint (`key_…`)."}],"responses":{"200":{"description":"Revoked. `keys` is the store-re-read list, including this revoked row.","content":{"application/json":{"schema":{"type":"object","properties":{"revoked":{"type":"string","description":"Id of the revoked key."},"keys":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"prefix":{"type":"string","description":"Non-secret prefix for later display."},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"label":{"type":"string"},"scope":{"type":"string","enum":["read","write"]},"last_used_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"last_read_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"last_write_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"skipped_read_count":{"type":"integer","minimum":0,"maximum":9007199254740991},"process_mode":{"type":"string","enum":["off","warn"]},"read_window_minutes":{"type":"integer","minimum":1,"maximum":10080},"quiet_alert_enabled":{"type":"boolean"},"quiet_alert_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"failed_calls_alert_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"failed_today":{"type":"integer","minimum":0,"maximum":9007199254740991},"last_fail_fix":{"type":"string","maxLength":280},"reads_last_7d":{"type":"integer","minimum":0,"maximum":9007199254740991},"usage_count":{"type":"integer","minimum":0,"maximum":9007199254740991},"revoked_at":{"description":"Set when the key is revoked. Omitted while live.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T"},"lane":{"description":"When set, this key is bounded to that lane.","type":"string","maxLength":64}},"required":["id","prefix","created_at"],"additionalProperties":false},"description":"Owner key list re-read from the store after this revoke, including revoked keys."}},"required":["revoked","keys"],"additionalProperties":false}}}},"401":{"description":"Auth is configured and there is no owner session","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown, already revoked, or not this owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks":{"get":{"tags":["webhooks"],"summary":"List outbound webhooks","description":"Hashed key required. Returns this owner's webhook endpoints and the last 100 deliveries. Never returns owner_id or signing secrets.","operationId":"listWebhooks","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"Owner webhooks and delivery log","content":{"application/json":{"schema":{"type":"object","required":["webhooks","deliveries"],"properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}},"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}}}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["webhooks"],"summary":"Create an outbound webhook","description":"Write-scoped hashed key required. Body: {url, events}. url must be public https (same refusal rules as fetch). events is a non-empty subset of run.finished and note.stored. Returns the endpoint plus secret once. MCP keys action settings with url/events is the same write.","operationId":"createWebhook","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"events":{"type":"array","items":{"type":"string","enum":["run.finished","note.stored","usage.spike","process.skipped_read","process.claim_contradicted"]}}},"required":["url","events"],"additionalProperties":false}}}},"responses":{"201":{"description":"Created. Copy the secret now; it is not shown again.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"object","required":["secret"],"properties":{"secret":{"type":"string","description":"HMAC signing secret, shown once. Never stored in API responses after this."}}}]}}}},"400":{"description":"invalid_webhook_url, invalid_webhook_events, webhook_limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Read-scoped key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["webhooks"],"summary":"Delete an outbound webhook","description":"Write-scoped hashed key required. Identify by ?id= or JSON {id}. Scoped to the caller's owner. MCP keys action settings with id + settings_op delete is the same write.","operationId":"deleteWebhook","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"query","required":false,"description":"Webhook id (wh_…). Also accepted as JSON body {id}.","schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","required":["deleted"],"properties":{"deleted":{"type":"string"}}}}}},"400":{"description":"id_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown id or another owner's webhook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/owner/us-confirmation":{"post":{"tags":["owner"],"summary":"Confirm United States location","description":"Signed-in owner session only. Stores us_confirmed_at for existing owners who signed up before the 3.110 checkbox. Idempotent if already stored. 401 without a session. API keys cannot call this.","operationId":"confirmOwnerUsLocation","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"us_confirmation":{"type":"boolean","description":"Required. Confirms United States location and agreement to the Terms and Privacy Policy."}},"required":["us_confirmation"],"additionalProperties":false}}}},"responses":{"200":{"description":"Confirmation stored","content":{"application/json":{"schema":{"type":"object","properties":{"us_confirmed_at":{"type":"string","description":"ISO timestamp when the owner confirmed."}},"required":["us_confirmed_at"],"additionalProperties":false}}}},"400":{"description":"us_confirmation_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized — sign in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/owner":{"get":{"tags":["owner"],"summary":"Owner settings","description":"Hashed key required. Returns retention_days (default 90), digest_webhook_configured, digest_webhook_url when set, and retentionBacklog {overdue, oldestAgeDays, state, pinnedSkipped} for this owner only. pinned pages are kept past the plan's retention window; unpin to let one expire. state is ok | unknown. Never returns owner_id or the webhook secret.","operationId":"getOwnerSettings","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"Owner settings","content":{"application/json":{"schema":{"type":"object","required":["retention_days","digest_webhook_configured","quotas","retentionBacklog"],"properties":{"retention_days":{"type":"integer","minimum":1,"maximum":3650},"digest_webhook_configured":{"type":"boolean"},"digest_webhook_url":{"type":"string","format":"uri"},"last_digest_at":{"type":"string","format":"date-time"},"quotas":{"$ref":"#/components/schemas/UsageQuotas"},"retentionBacklog":{"type":"object","required":["overdue","oldestAgeDays","state","pinnedSkipped"],"properties":{"overdue":{"type":"integer","minimum":0},"oldestAgeDays":{"type":["integer","null"],"minimum":0},"state":{"type":"string","enum":["ok","unknown"]},"pinnedSkipped":{"type":"integer","minimum":0}}}}}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["owner"],"summary":"Delete my data","description":"Wipes this owner's runs, notes, keys, idempotency fingerprints, fetch cache, and the Supabase Auth identity for the owner's email when one exists (service-role admin delete). Fails with 502 auth_identity_delete_failed rather than silently keeping the email. Seeds stay. Signed-in owner session or a write-scoped hashed key. Never returns secrets or hashes.","operationId":"deleteOwnerData","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"Deleted counts plus auth_identity (deleted | absent | none)","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"object","properties":{"runs":{"type":"integer","minimum":0,"maximum":9007199254740991},"notes":{"type":"integer","minimum":0,"maximum":9007199254740991},"keys":{"type":"integer","minimum":0,"maximum":9007199254740991},"idempotency":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetch_cache":{"type":"integer","minimum":0,"maximum":9007199254740991},"usage":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["runs","notes","keys","idempotency","fetch_cache"],"additionalProperties":false},"auth_identity":{"type":"string","enum":["deleted","absent","none"],"description":"deleted = auth.users row removed; absent = no row for this email; none = no Auth store (empty env) or no email on this owner."}},"required":["deleted","auth_identity"],"additionalProperties":false}}}},"401":{"description":"Missing session or write key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"auth_identity_delete_failed — Auth row existed or Auth is configured and the admin delete could not run","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["owner"],"summary":"Set owner settings","description":"Write-scoped hashed key required. Optional retention_days (1–3650), digest_webhook_url plus digest_webhook_secret (16–256 chars, public http(s) only), and quotas (per-key daily soft caps). Null URL clears the webhook. The secret is write-only and never returned.","operationId":"patchOwnerSettings","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"retention_days":{"type":"integer","minimum":1,"maximum":3650},"digest_webhook_url":{"type":["string","null"]},"digest_webhook_secret":{"type":"string","minLength":16,"maxLength":256},"quotas":{"type":"object","properties":{"requests":{"type":"integer","minimum":0,"maximum":9007199254740991},"runs":{"type":"integer","minimum":0,"maximum":9007199254740991},"notes":{"type":"integer","minimum":0,"maximum":9007199254740991},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetches":{"type":"integer","minimum":0,"maximum":9007199254740991},"bytes":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["requests","runs","notes","fetches","bytes"],"additionalProperties":false}},"additionalProperties":false}}}},"responses":{"200":{"description":"Updated settings","content":{"application/json":{"schema":{"type":"object","required":["retention_days","digest_webhook_configured","quotas","retentionBacklog"],"properties":{"retention_days":{"type":"integer"},"digest_webhook_configured":{"type":"boolean"},"digest_webhook_url":{"type":"string","format":"uri"},"last_digest_at":{"type":"string","format":"date-time"},"quotas":{"$ref":"#/components/schemas/UsageQuotas"},"retentionBacklog":{"type":"object","required":["overdue","oldestAgeDays","state","pinnedSkipped"],"properties":{"overdue":{"type":"integer","minimum":0},"oldestAgeDays":{"type":["integer","null"],"minimum":0},"state":{"type":"string","enum":["ok","unknown"]},"pinnedSkipped":{"type":"integer","minimum":0}}}}}}}},"400":{"description":"invalid_retention_days, invalid_digest_webhook_url, invalid_digest_webhook_secret, invalid_quotas, nothing_to_patch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/usage":{"get":{"tags":["usage"],"summary":"Workspace usage meter","description":"Hashed owner key required. Returns this UTC month (`period`) plus last 30 days of workspace usage_daily (runs, notes, reads, fetches, fetch_bytes, tokens_out), plan quotas, `ais` (active keys plus active Connect grants; over the limit is a notice, never a cutoff), `limits` (used, limit, percent and reset_at for AIs, notes, reads, runs and page fetches), included units, per-key daily rollups (including additive `routes` route-to-count map and `status_2xx`/`status_4xx`/`status_5xx` plus `fail_routes`), and per-key last_request_at plus quiet_for_hours plus reads_before_writes_missed (writes with no recent read) for each active key, and a workspace total. Never returns owner_id, hashes, or plaintext secrets. Allowed when over quota so owners can inspect usage.","operationId":"getOwnerUsage","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"Usage rollups","content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string"},"quotas":{"type":"object","properties":{"requests":{"type":"integer","minimum":0,"maximum":9007199254740991},"runs":{"type":"integer","minimum":0,"maximum":9007199254740991},"notes":{"type":"integer","minimum":0,"maximum":9007199254740991},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetches":{"type":"integer","minimum":0,"maximum":9007199254740991},"bytes":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["requests","runs","notes","fetches","bytes"],"additionalProperties":false},"ais":{"type":"object","properties":{"used":{"type":"integer","minimum":0,"maximum":9007199254740991},"limit":{"type":"integer","minimum":0,"maximum":9007199254740991},"over_limit":{"type":"boolean"},"notice":{"type":"string"}},"required":["used","limit","over_limit"],"additionalProperties":false,"description":"AIs = active API keys plus active Connect grants. Over the limit is a notice, never a cutoff."},"limits":{"type":"array","items":{"type":"object","properties":{"metric":{"type":"string","enum":["ais","notes","reads","runs","fetches"]},"label":{"type":"string"},"used":{"type":"integer","minimum":0,"maximum":9007199254740991},"limit":{"type":"integer","minimum":0,"maximum":9007199254740991},"percent":{"type":"number","minimum":0},"reset_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"over_limit":{"type":"boolean"}},"required":["metric","label","used","limit","percent","reset_at","over_limit"],"additionalProperties":false},"description":"Used against the plan limit with percent and reset date: AIs, notes, reads, runs, fetches."},"reset_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"period":{"type":"object","properties":{"month":{"type":"string"},"from":{"type":"string"},"until":{"type":"string"},"runs":{"type":"integer","minimum":0,"maximum":9007199254740991},"notes":{"type":"integer","minimum":0,"maximum":9007199254740991},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetches":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetch_bytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"tokens_out":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["month","from","until","runs","notes","reads","fetches","fetch_bytes","tokens_out"],"additionalProperties":false},"included":{"type":"object","properties":{"runs":{"type":"integer","minimum":0,"maximum":9007199254740991},"notes":{"type":"integer","minimum":0,"maximum":9007199254740991},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetches":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["runs","notes","reads","fetches"],"additionalProperties":false},"overage_units":{"type":"object","properties":{"runs":{"type":"integer","minimum":0,"maximum":9007199254740991},"notes":{"type":"integer","minimum":0,"maximum":9007199254740991},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetches":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["runs","notes","reads","fetches"],"additionalProperties":false},"projected_overage_usd":{"type":"number","minimum":0},"last_30_days":{"type":"array","items":{"type":"object","properties":{"day":{"type":"string"},"runs":{"type":"integer","minimum":0,"maximum":9007199254740991},"notes":{"type":"integer","minimum":0,"maximum":9007199254740991},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetches":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetch_bytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"tokens_out":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["day","runs","notes","reads","fetches","fetch_bytes","tokens_out"],"additionalProperties":false}},"days":{"type":"array","items":{"type":"object","properties":{"key_id":{"type":"string"},"prefix":{"type":"string"},"label":{"type":"string"},"day":{"type":"string"},"requests":{"type":"integer","minimum":0,"maximum":9007199254740991},"runs":{"type":"integer","minimum":0,"maximum":9007199254740991},"notes":{"type":"integer","minimum":0,"maximum":9007199254740991},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetches":{"type":"integer","minimum":0,"maximum":9007199254740991},"bytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"routes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"integer","minimum":0,"maximum":9007199254740991}},"status_2xx":{"type":"integer","minimum":0,"maximum":9007199254740991},"status_4xx":{"type":"integer","minimum":0,"maximum":9007199254740991},"status_5xx":{"type":"integer","minimum":0,"maximum":9007199254740991},"fail_routes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"status":{"type":"integer","minimum":400,"maximum":599},"count":{"type":"integer","minimum":0,"maximum":9007199254740991},"last_code":{"type":"string"},"last_fix":{"type":"string"}},"required":["status","count"],"additionalProperties":false},"description":"Small map of `<route>:<status>` to {status, count} for the most common failures."}},"required":["key_id","day","requests","runs","notes","reads","fetches","bytes","status_2xx","status_4xx","status_5xx","fail_routes"],"additionalProperties":false}},"keys":{"type":"array","items":{"type":"object","properties":{"key_id":{"type":"string"},"prefix":{"type":"string"},"label":{"type":"string"},"last_request_at":{"type":["string","null"]},"quiet_for_hours":{"type":"integer","minimum":0,"maximum":9007199254740991},"reads_before_writes_missed":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["key_id","last_request_at","quiet_for_hours","reads_before_writes_missed"],"additionalProperties":false}},"reads_before_writes_missed":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["plan","quotas","ais","limits","reset_at","period","included","overage_units","projected_overage_usd","last_30_days","days","keys","reads_before_writes_missed"],"additionalProperties":false}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/export":{"get":{"tags":["export"],"summary":"Export owner runs and notes","description":"Hashed owner key required. JSON of persisted owner runs, notes, usage rollups, and quotas — no seed samples, no raw API secrets, no hashes. Operator may be a key prefix. Run rows include status, started_at, finished_at, duration_ms, tags, and summary. Note rows include optional title and pinned.","operationId":"exportOwner","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"Owner bundle","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OwnerExport"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["export"],"summary":"Queue a JSON + markdown zip","description":"Write-scoped hashed key (or signed-in owner session). Queues a zip of runs, notes, key metadata, and usage. Served at GET /v1/export/{id} for 7 days. Writes an audit_events row. Never includes secrets or hashes.","operationId":"queueOwnerExport","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"responses":{"201":{"description":"Queued zip job (ready immediately)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportJob"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/export/{id}":{"get":{"tags":["export"],"summary":"Download a queued export zip","description":"Hashed owner key (or session) required. Returns the zip for 7 days after POST /v1/export. 404 when missing, expired, or not this owner.","operationId":"getOwnerExportZip","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"application/zip","content":{"application/zip":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown, expired, or other-owner export","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspace":{"patch":{"tags":["export"],"summary":"Set process-alert destination","description":"Write-scoped key or signed-in session. Sets alert_webhook_url (public https) and/or alert_email. Webhook secret shown once. HMAC delivery uses the existing webhook path. Email alerts are not sent yet. Set alert_webhook_url to receive alerts today; alert_email is saved for when email alerts launch.","operationId":"patchWorkspace","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"alert_webhook_url":{"type":["string","null"]},"alert_email":{"type":["string","null"]}},"additionalProperties":false}}}},"responses":{"200":{"description":"Destination saved. Secret shown once when a new webhook URL is set.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"invalid_webhook_url or invalid_alert_email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["export"],"summary":"Workspace status","description":"Hashed key required. Returns id, plan, deleted_at, and cancel_until when soft-deleted.","operationId":"getWorkspace","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"Workspace status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceStatus"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["export"],"summary":"Soft-delete workspace","description":"Write-scoped key or signed-in session. Soft-deletes the caller's workspace with a 30-day cancel window. Writes an audit_events row. Immediate wipe remains DELETE /v1/owner.","operationId":"deleteWorkspace","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"Soft-deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceDelete"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"already_deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["export"],"summary":"Cancel workspace delete","description":"Write-scoped key or signed-in session. Cancels a soft-delete within 30 days. Writes an audit_events row. 410 after the window.","operationId":"cancelWorkspaceDelete","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"Cancelled","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["cancelled","workspace"],"properties":{"cancelled":{"type":"boolean"},"workspace":{"$ref":"#/components/schemas/WorkspaceStatus"}}}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"not_deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"cancel_expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/billing/checkout":{"post":{"tags":["billing"],"summary":"Create a Checkout session","description":"Owner session or unrestricted write key. Returns a hosted Checkout URL for solo|team and month|year. Solo is $15/month or $144/year. Team is $49/month or $490/year. 404 when is off. Included quantities come from the plans table. Stripe Customer is created once per workspace. Billing routes return 404 until paid plans open.","operationId":"createBillingCheckout","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string","enum":["solo","team"]},"interval":{"type":"string","enum":["month","year"]}},"required":["plan","interval"],"additionalProperties":false}}}},"responses":{"200":{"description":"Checkout URL","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"plan":{"type":"string","enum":["solo","team"]},"interval":{"type":"string","enum":["month","year"]},"included":{"type":"object","properties":{"runs":{"type":"integer","minimum":0,"maximum":9007199254740991},"notes":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetches":{"type":"integer","minimum":0,"maximum":9007199254740991},"keys":{"type":"integer","minimum":0,"maximum":9007199254740991},"retention_days":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["runs","notes","fetches","keys","retention_days"],"additionalProperties":false}},"required":["url","plan","interval","included"],"additionalProperties":false}}}},"400":{"description":"invalid_plan or invalid_interval","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing session or key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"lane_forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Billing routes return 404 until paid plans open.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/billing/change":{"post":{"tags":["billing"],"summary":"Change plan","description":"Owner session or unrestricted write key. Paid target with no subscription returns a Checkout URL; with a subscription: upgrades apply now with proration (effective: now); downgrades are scheduled for the end of the paid term with no credit (effective: period_end, effective_at) and need confirm:true (409 confirm_required carries the date). Free on a subscription cancels at period end (effective_at); Free with no subscription is immediate and needs confirm:true (409 confirm_required otherwise). 404 when is off. Billing routes return 404 until paid plans open.","operationId":"changeBillingPlan","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string","enum":["free","solo","team"]},"interval":{"type":"string","enum":["month","year"]},"confirm":{"type":"boolean"}},"required":["plan"],"additionalProperties":false}}}},"responses":{"200":{"description":"Change started or applied","content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string","enum":["free","solo","team"]},"url":{"type":"string"},"via":{"type":"string","enum":["checkout","portal"]},"effective":{"type":"string","enum":["now","period_end"]},"effective_at":{"type":["string","null"]}},"required":["plan"],"additionalProperties":false}}}},"400":{"description":"invalid_plan or invalid_interval","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing session or key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"lane_forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Billing routes return 404 until paid plans open.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"confirm_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/billing/portal":{"post":{"tags":["billing"],"summary":"Create a Customer Portal session","description":"Owner session or unrestricted write key. Returns a Customer Portal URL. 404 when is off. Billing routes return 404 until paid plans open.","operationId":"createBillingPortal","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"Portal URL","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"],"additionalProperties":false}}}},"401":{"description":"Missing session or key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Billing routes return 404 until paid plans open.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/billing/settings":{"get":{"tags":["billing"],"summary":"Billing settings","description":"Owner session or unrestricted write key. Returns the workspace billing settings. 404 when is off. Billing routes return 404 until paid plans open.","operationId":"getBillingSettings","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"Current billing settings","content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string"},"billing_status":{"type":"string"},"overage":{"type":"boolean"},"spend_cap_usd":{"type":["number","null"]}},"required":["plan","billing_status","overage","spend_cap_usd"],"additionalProperties":false}}}},"404":{"description":"Billing routes return 404 until paid plans open.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["billing"],"summary":"Update billing settings","description":"Owner session or unrestricted write key (F6 tier 1). Not available at launch: returns 404. Billing routes return 404 until paid plans open.","operationId":"patchBillingSettings","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"overage":{"type":"boolean"},"spend_cap_usd":{"type":["number","null"]}},"additionalProperties":false}}}},"responses":{"200":{"description":"Updated settings","content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string"},"billing_status":{"type":"string"},"overage":{"type":"boolean"},"spend_cap_usd":{"type":["number","null"]}},"required":["plan","billing_status","overage","spend_cap_usd"],"additionalProperties":false}}}},"400":{"description":"invalid_request, nothing_to_patch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Billing routes return 404 until paid plans open.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/what_happened_last":{"get":{"tags":["handoff"],"summary":"Session handoff","description":"Hashed owner key required. Workspace-scoped read of the last N runs with their summaries so a caller does not need a run id, plus recent notes (pinned first) as markdown. Each markdown page is capped at 300 whitespace tokens and max_chars. The `runs` array is the same last N workspace rows (id, created_at, goal, outcome, status, summary). The `notes` array is the matching notes that fit that budget (id, title, body, tags, pinned, created_at); each body is sliced by max_chars. `notes_omitted` is how many matching notes that array leaves out. A missing `notes` key is a contract break, not an empty shelf. The continuation cursor pages the markdown string only — `runs` and `notes` stay the same across pages. THE read call is `?lane=<name>` (alias for `tags=lane:<name>`; comma-separated tags all must match, case-insensitive) as a first-class filter alongside `project` and `routine`. The md digest and the JSON both echo `applied: {tags: [...]}` (plus project/routine when set). Optional `project` and `routine` match a tag or a substring of run goal / note title. `n` is 1–20 (default 5). Optional max_chars (default 4000) and cursor (char offset). Memoshelf system notes (kind system) are left out unless `include_system=true`. Accepted query parameters: tags, lane, project, routine, n, include_system, max_chars, cursor. Unknown keys return warnings, not 400. Seeds stay out. MCP `what_happened_last` is the same payload.","operationId":"whatHappenedLast","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"tags","in":"query","required":false,"description":"Comma-separated tags. All must match, case-insensitive. THE read call is GET /v1/what_happened_last?lane=<name> (alias for tags=lane:<name>). Unknown query keys return warnings, not 400.","schema":{"type":"string"}},{"name":"lane","in":"query","required":false,"description":"Alias for tags=lane:<name>. Same result as tags=lane:<name>.","schema":{"type":"string"}},{"name":"include_system","in":"query","required":false,"description":"true also returns Memoshelf system notes (kind system). Default false.","schema":{"type":"boolean"}},{"name":"project","in":"query","required":false,"description":"Filter by tag or run goal / note title (case-insensitive).","schema":{"type":"string","maxLength":80}},{"name":"routine","in":"query","required":false,"description":"Filter by tag or run goal / note title (case-insensitive).","schema":{"type":"string","maxLength":80}},{"name":"n","in":"query","required":false,"description":"How many latest matching runs to include. Default 5, max 20.","schema":{"type":"integer","default":5,"minimum":1,"maximum":20}},{"name":"max_chars","in":"query","required":false,"description":"Character budget for this read. Default 4000, max 80000. Responses include truncated and a continuation next_cursor.","schema":{"type":"integer","default":4000,"minimum":1,"maximum":80000}},{"name":"cursor","in":"query","required":false,"description":"Character offset into the handoff markdown from a previous next_cursor.","schema":{"type":"string"}}],"responses":{"200":{"description":"Handoff markdown plus last N workspace runs with summaries and a notes array","content":{"application/json":{"schema":{"type":"object","properties":{"markdown":{"type":"string"},"tokens":{"type":"integer","minimum":0,"maximum":300},"n":{"type":"integer","minimum":1,"maximum":20},"truncated":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"runs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"goal":{"type":"string"},"outcome":{"type":"string"},"status":{"type":"string"},"summary":{"description":"Small JSON from the run, when present.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["id","created_at","goal","outcome"],"additionalProperties":false},"description":"Last N workspace-scoped runs with summaries. Same page as the markdown; no run id required."},"notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"body":{"type":"string","description":"Note body, sliced by max_chars. The continuation cursor still pages the markdown string only."},"tags":{"type":"array","items":{"type":"string"}},"pinned":{"type":"boolean"},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"kind":{"type":"string","enum":["user","system"]},"level":{"type":"string","enum":["info","warn","action"]},"operator":{"type":"string"},"expires_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"acked_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"ack_state":{"type":"string","enum":["unacked","acked","expired"]}},"required":["id","body","created_at"],"additionalProperties":false},"description":"Matching notes that fit the 300-token markdown budget (unacked system notes first, then pinned). id, title, body, tags, pinned, created_at. notes_omitted counts matching notes this array leaves out."},"notes_omitted":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Matching notes not included in notes because they did not fit the 300-token budget. Pinned notes are tried first."},"applied":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"},"description":"Tag filters actually applied. All must match, case-insensitive."},"project":{"type":"string"},"routine":{"type":"string"}},"required":["tags"],"additionalProperties":false,"description":"Filters actually applied so a caller can assert them. Echoed in markdown as `applied: {…}`."},"warnings":{"description":"Unknown query keys that were ignored. Present only when the caller sent one.","type":"array","items":{"type":"object","properties":{"param":{"type":"string"},"fix":{"type":"string"}},"required":["param","fix"],"additionalProperties":false}}},"required":["markdown","tokens","n","truncated","next_cursor","runs","notes","notes_omitted","applied"],"additionalProperties":false}}}},"400":{"description":"invalid_n, invalid_project, invalid_routine, invalid_tags, invalid_lane, invalid_max_chars, or invalid_cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/mcp":{"post":{"tags":["mcp"],"summary":"Streamable HTTP MCP (JSON-RPC)","description":"JSON-RPC 2.0 for common MCP clients. Every POST requires a hashed key or Connect token. Unauthenticated POST is 401 with WWW-Authenticate pointing at the protected-resource metadata.","operationId":"mcpJsonRpc","security":[{},{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"description":"Omit on notifications.","type":["string","number","null"]},"method":{"type":"string"},"params":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["jsonrpc","method"],"additionalProperties":false}}}},"responses":{"200":{"description":"JSON-RPC result (initialize, tools/list, tools/call, ping)","content":{"application/json":{"schema":{"type":"object"}}}},"202":{"description":"Notification ack (notifications/initialized). Empty body. No JSON-RPC error."},"401":{"description":"Missing or unknown key or expired grant, including initialize and tools/list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"tools/call get_run or get_note or delete_run or delete_note or update_run or update_note or revoke_key: missing id or a seed sample or unknown/already-revoked key (same as REST GET-by-id / DELETE / PATCH / key revoke)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"JSON body over KB (runs/notes) or md URL string over KB. MCP tool args use the same caps.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"public_url_to_md or fetch_md over the cap","headers":{"X-RateLimit-Limit":{"description":"Max markdown fetches per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining markdown fetches in the current window.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before retrying a 429.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["mcp"],"summary":"Start Connect when unauthenticated","description":"GET /mcp without credentials is 401 so common MCP clients start sign-in. A valid key still gets 405 (use POST).","operationId":"mcpGetNotAllowed","security":[],"responses":{"401":{"description":"Missing or unknown key — start Connect","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"Authenticated GET — use POST","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["mcp"],"summary":"Method not allowed","description":"DELETE /mcp is 405. Handshake is POST-only. Not an auth miss.","operationId":"mcpDeleteNotAllowed","security":[],"responses":{"405":{"description":"Use POST","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/export.csv":{"get":{"tags":["export"],"summary":"Export owner runs and notes as CSV","description":"Hashed owner key required. CSV of persisted owner runs and notes only — no seed samples, no raw API secrets, no hashes. Operator may be a key prefix. Columns: kind,id,goal,tools,outcome,body,tags,operator,created_at,status,started_at,finished_at,duration_ms,summary,expected_by,title,pinned.","operationId":"exportOwnerCsv","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"Owner CSV","content":{"text/csv":{"schema":{"type":"string"}}}},"401":{"description":"Missing or unknown key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/openapi.json":{"get":{"tags":["health"],"summary":"OpenAPI 3.1 document","description":"Same generated spec as GET /openapi.json and GET /.well-known/openapi.json. No auth. No secrets. Built from the route zod schemas.","security":[],"responses":{"200":{"description":"OpenAPI 3.1 JSON","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}},"operationId":"getOpenApiV1"}},"/openapi.json":{"get":{"tags":["health"],"summary":"OpenAPI 3.1 document","description":"Same generated spec as GET /openapi.json and GET /.well-known/openapi.json. No auth. No secrets. Built from the route zod schemas.","security":[],"responses":{"200":{"description":"OpenAPI 3.1 JSON","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}},"operationId":"getOpenApi"}},"/.well-known/openapi.json":{"get":{"tags":["health"],"summary":"OpenAPI 3.1 document","description":"Same generated spec as GET /openapi.json and GET /.well-known/openapi.json. No auth. No secrets. Built from the route zod schemas.","security":[],"responses":{"200":{"description":"OpenAPI 3.1 JSON","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}},"operationId":"getOpenApiWellKnown"}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Hashed API key. Send the plaintext `ms_…` secret shown once at mint."},"apiKeyHeader":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"Same secret as Bearer. Hashed at rest."}},"schemas":{"Health":{"type":"object","required":["ok","version","product","env","dataSource","degraded"],"properties":{"ok":{"const":true},"version":{"type":"string"},"product":{"const":"memoshelf"},"env":{"enum":["production","sandbox"]},"dataSource":{"type":"string"},"db_unreachable":{"type":["string","null"]},"degraded":{"type":"boolean"},"migrations":{"type":"object"},"crons":{"type":"object"},"lastStrangerPath":{"type":"object"}}},"OwnerAppKey":{"type":"object","properties":{"id":{"type":"string"},"prefix":{"type":"string","description":"Non-secret prefix for later display."},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"label":{"type":"string"},"scope":{"type":"string","enum":["read","write"]},"last_used_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"last_read_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"last_write_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"skipped_read_count":{"type":"integer","minimum":0,"maximum":9007199254740991},"process_mode":{"type":"string","enum":["off","warn"]},"read_window_minutes":{"type":"integer","minimum":1,"maximum":10080},"quiet_alert_enabled":{"type":"boolean"},"quiet_alert_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"failed_calls_alert_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"failed_today":{"type":"integer","minimum":0,"maximum":9007199254740991},"last_fail_fix":{"type":"string","maxLength":280},"reads_last_7d":{"type":"integer","minimum":0,"maximum":9007199254740991},"usage_count":{"type":"integer","minimum":0,"maximum":9007199254740991},"revoked_at":{"description":"Set when the key is revoked. Omitted while live.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T"},"lane":{"description":"When set, this key is bounded to that lane.","type":"string","maxLength":64}},"required":["id","prefix","created_at"],"additionalProperties":false},"MintedKey":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","description":"Plaintext secret, shown once. Shape `ms_…`. Never stored."},"prefix":{"type":"string","description":"Non-secret prefix for later display."},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"name":{"type":"string"},"label":{"type":"string"},"scope":{"type":"string","enum":["read","write"]},"lane":{"type":"string"},"keys":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"prefix":{"type":"string","description":"Non-secret prefix for later display."},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"label":{"type":"string"},"scope":{"type":"string","enum":["read","write"]},"last_used_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"last_read_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"last_write_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"skipped_read_count":{"type":"integer","minimum":0,"maximum":9007199254740991},"process_mode":{"type":"string","enum":["off","warn"]},"read_window_minutes":{"type":"integer","minimum":1,"maximum":10080},"quiet_alert_enabled":{"type":"boolean"},"quiet_alert_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"failed_calls_alert_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"failed_today":{"type":"integer","minimum":0,"maximum":9007199254740991},"last_fail_fix":{"type":"string","maxLength":280},"reads_last_7d":{"type":"integer","minimum":0,"maximum":9007199254740991},"usage_count":{"type":"integer","minimum":0,"maximum":9007199254740991},"revoked_at":{"description":"Set when the key is revoked. Omitted while live.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T"},"lane":{"description":"When set, this key is bounded to that lane.","type":"string","maxLength":64}},"required":["id","prefix","created_at"],"additionalProperties":false},"description":"Owner key list re-read from the store after this mint, including revoked keys. Never includes the plaintext secret."}},"required":["id","key","prefix","created_at","keys"],"additionalProperties":false},"RevokedKey":{"type":"object","properties":{"revoked":{"type":"string","description":"Id of the revoked key."},"keys":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"prefix":{"type":"string","description":"Non-secret prefix for later display."},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"label":{"type":"string"},"scope":{"type":"string","enum":["read","write"]},"last_used_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"last_read_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"last_write_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"skipped_read_count":{"type":"integer","minimum":0,"maximum":9007199254740991},"process_mode":{"type":"string","enum":["off","warn"]},"read_window_minutes":{"type":"integer","minimum":1,"maximum":10080},"quiet_alert_enabled":{"type":"boolean"},"quiet_alert_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"failed_calls_alert_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"failed_today":{"type":"integer","minimum":0,"maximum":9007199254740991},"last_fail_fix":{"type":"string","maxLength":280},"reads_last_7d":{"type":"integer","minimum":0,"maximum":9007199254740991},"usage_count":{"type":"integer","minimum":0,"maximum":9007199254740991},"revoked_at":{"description":"Set when the key is revoked. Omitted while live.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T"},"lane":{"description":"When set, this key is bounded to that lane.","type":"string","maxLength":64}},"required":["id","prefix","created_at"],"additionalProperties":false},"description":"Owner key list re-read from the store after this revoke, including revoked keys."}},"required":["revoked","keys"],"additionalProperties":false},"Run":{"type":"object","properties":{"id":{"type":"string"},"goal":{"type":"string"},"tools":{"type":"array","items":{"type":"string"}},"outcome":{"type":"string"},"operator":{"type":"string","description":"Key prefix or seed operator name. Not a secret."},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"status":{"type":"string","enum":["started","succeeded","failed"]},"started_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"finished_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"duration_ms":{"type":"integer","minimum":0,"maximum":9007199254740991},"tags":{"type":"array","items":{"type":"string"}},"summary":{"description":"Small JSON, max KB.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"expected_by":{"description":"Optional ISO-8601 deadline. Started-but-unfinished runs past this are stale.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T"},"seed":{"description":"Present on public demo samples only.","type":"boolean"},"read_before_write":{"description":"True when this write followed a qualifying read in the key's window.","type":"boolean"},"warnings":{"description":"Present on create when process-compliance mode is warn and a check fired.","type":"array","items":{"type":"string","enum":["no_read_before_write","process_claim_contradicted"]}}},"required":["id","goal","tools","outcome","operator","created_at","status","started_at","tags"],"additionalProperties":false},"Note":{"type":"object","properties":{"id":{"type":"string"},"title":{"description":"Optional. Max 200 characters.","type":"string","maxLength":200},"body":{"type":"string","description":"Required on create. Max 32KB."},"tags":{"type":"array","items":{"type":"string"}},"operator":{"type":"string","description":"Key prefix or seed operator name. Not a secret."},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"pinned":{"description":"When true, the note is listed after unacked system notes in what_happened_last.","type":"boolean"},"seed":{"description":"Present on public demo samples only.","type":"boolean"},"read_before_write":{"description":"True when this write followed a qualifying read in the key's window.","type":"boolean"},"warnings":{"description":"Present on create when process-compliance mode is warn and a check fired.","type":"array","items":{"type":"string","enum":["no_read_before_write","process_claim_contradicted"]}},"kind":{"description":"system notes are written by Memoshelf, never by an API key.","type":"string","enum":["user","system"]},"level":{"description":"Present on system notes.","type":"string","enum":["info","warn","action"]},"expires_at":{"description":"Optional expiry on a system note.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T"},"acked_at":{"description":"When the system note was acknowledged.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T"},"ack_rule":{"type":"string","enum":["write","explicit","meter"]},"system_code":{"type":"string"},"ack_state":{"type":"string","enum":["unacked","acked","expired"]}},"required":["id","body","tags","operator","created_at"],"additionalProperties":false},"Webhook":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string","enum":["run.finished","note.stored","usage.spike","process.skipped_read","process.claim_contradicted"]}},"enabled":{"type":"boolean"},"consecutive_failures":{"type":"integer","minimum":0,"maximum":9007199254740991},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"disabled_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"last_delivered_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"}},"required":["id","url","events","enabled","consecutive_failures","created_at"],"additionalProperties":false},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string"},"webhook_id":{"type":"string"},"event":{"type":"string","enum":["run.finished","note.stored","usage.spike","process.skipped_read","process.claim_contradicted"]},"status":{"type":"string","enum":["delivered","failed"]},"attempt":{"type":"integer","minimum":1,"maximum":9007199254740991},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"status_code":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"error":{"type":"string"}},"required":["id","webhook_id","event","status","attempt","created_at"],"additionalProperties":false},"Error":{"type":"object","properties":{"error":{"type":"string","description":"Same snake_case token as code."},"code":{"type":"string","description":"Stable machine code. 400/401/404/413/429 always include this."},"retry_after":{"type":"integer","minimum":0,"maximum":9007199254740991},"reset_at":{"type":"string"},"metric":{"type":"string"},"limit":{"type":"integer","minimum":0,"maximum":9007199254740991},"id":{"description":"On 409 idempotency_key_reuse: id of the record the key already points at.","type":"string"},"created_at":{"description":"On 409 idempotency_key_reuse: created-at of that record.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T"},"param":{"description":"On a known-setting error: the query key when relevant.","type":"string"},"fix":{"description":"One-sentence instruction an AI can act on. Present on every 4xx.","type":"string"},"help":{"description":"Path to the matching Something not working? item on /help#help.","type":"string"}},"required":["error","code"],"additionalProperties":false},"UsageQuotas":{"type":"object","properties":{"requests":{"type":"integer","minimum":0,"maximum":9007199254740991},"runs":{"type":"integer","minimum":0,"maximum":9007199254740991},"notes":{"type":"integer","minimum":0,"maximum":9007199254740991},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetches":{"type":"integer","minimum":0,"maximum":9007199254740991},"bytes":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["requests","runs","notes","fetches","bytes"],"additionalProperties":false},"UsageDay":{"type":"object","properties":{"key_id":{"type":"string"},"prefix":{"type":"string"},"label":{"type":"string"},"day":{"type":"string"},"requests":{"type":"integer","minimum":0,"maximum":9007199254740991},"runs":{"type":"integer","minimum":0,"maximum":9007199254740991},"notes":{"type":"integer","minimum":0,"maximum":9007199254740991},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetches":{"type":"integer","minimum":0,"maximum":9007199254740991},"bytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"routes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"integer","minimum":0,"maximum":9007199254740991}},"status_2xx":{"type":"integer","minimum":0,"maximum":9007199254740991},"status_4xx":{"type":"integer","minimum":0,"maximum":9007199254740991},"status_5xx":{"type":"integer","minimum":0,"maximum":9007199254740991},"fail_routes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"status":{"type":"integer","minimum":400,"maximum":599},"count":{"type":"integer","minimum":0,"maximum":9007199254740991},"last_code":{"type":"string"},"last_fix":{"type":"string"}},"required":["status","count"],"additionalProperties":false},"description":"Small map of `<route>:<status>` to {status, count} for the most common failures."}},"required":["key_id","day","requests","runs","notes","reads","fetches","bytes","status_2xx","status_4xx","status_5xx","fail_routes"],"additionalProperties":false},"UsageKeyQuiet":{"type":"object","properties":{"key_id":{"type":"string"},"prefix":{"type":"string"},"label":{"type":"string"},"last_request_at":{"type":["string","null"]},"quiet_for_hours":{"type":"integer","minimum":0,"maximum":9007199254740991},"reads_before_writes_missed":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["key_id","last_request_at","quiet_for_hours","reads_before_writes_missed"],"additionalProperties":false},"WorkspaceUsageDay":{"type":"object","properties":{"day":{"type":"string"},"runs":{"type":"integer","minimum":0,"maximum":9007199254740991},"notes":{"type":"integer","minimum":0,"maximum":9007199254740991},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetches":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetch_bytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"tokens_out":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["day","runs","notes","reads","fetches","fetch_bytes","tokens_out"],"additionalProperties":false},"UsagePeriod":{"type":"object","properties":{"month":{"type":"string"},"from":{"type":"string"},"until":{"type":"string"},"runs":{"type":"integer","minimum":0,"maximum":9007199254740991},"notes":{"type":"integer","minimum":0,"maximum":9007199254740991},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetches":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetch_bytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"tokens_out":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["month","from","until","runs","notes","reads","fetches","fetch_bytes","tokens_out"],"additionalProperties":false},"OwnerExport":{"type":"object","properties":{"product":{"type":"string"},"version":{"type":"string"},"exported_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"runs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"goal":{"type":"string"},"tools":{"type":"array","items":{"type":"string"}},"outcome":{"type":"string"},"operator":{"type":"string","description":"Key prefix or seed operator name. Not a secret."},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"status":{"type":"string","enum":["started","succeeded","failed"]},"started_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"finished_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"duration_ms":{"type":"integer","minimum":0,"maximum":9007199254740991},"tags":{"type":"array","items":{"type":"string"}},"summary":{"description":"Small JSON, max KB.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"expected_by":{"description":"Optional ISO-8601 deadline. Started-but-unfinished runs past this are stale.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T"},"seed":{"description":"Present on public demo samples only.","type":"boolean"},"read_before_write":{"description":"True when this write followed a qualifying read in the key's window.","type":"boolean"},"warnings":{"description":"Present on create when process-compliance mode is warn and a check fired.","type":"array","items":{"type":"string","enum":["no_read_before_write","process_claim_contradicted"]}}},"required":["id","goal","tools","outcome","operator","created_at","status","started_at","tags"],"additionalProperties":false}},"notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"description":"Optional. Max 200 characters.","type":"string","maxLength":200},"body":{"type":"string","description":"Required on create. Max 32KB."},"tags":{"type":"array","items":{"type":"string"}},"operator":{"type":"string","description":"Key prefix or seed operator name. Not a secret."},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"pinned":{"description":"When true, the note is listed after unacked system notes in what_happened_last.","type":"boolean"},"seed":{"description":"Present on public demo samples only.","type":"boolean"},"read_before_write":{"description":"True when this write followed a qualifying read in the key's window.","type":"boolean"},"warnings":{"description":"Present on create when process-compliance mode is warn and a check fired.","type":"array","items":{"type":"string","enum":["no_read_before_write","process_claim_contradicted"]}},"kind":{"description":"system notes are written by Memoshelf, never by an API key.","type":"string","enum":["user","system"]},"level":{"description":"Present on system notes.","type":"string","enum":["info","warn","action"]},"expires_at":{"description":"Optional expiry on a system note.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T"},"acked_at":{"description":"When the system note was acknowledged.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T"},"ack_rule":{"type":"string","enum":["write","explicit","meter"]},"system_code":{"type":"string"},"ack_state":{"type":"string","enum":["unacked","acked","expired"]}},"required":["id","body","tags","operator","created_at"],"additionalProperties":false}},"usage":{"type":"array","items":{"type":"object","properties":{"key_id":{"type":"string"},"prefix":{"type":"string"},"label":{"type":"string"},"day":{"type":"string"},"requests":{"type":"integer","minimum":0,"maximum":9007199254740991},"runs":{"type":"integer","minimum":0,"maximum":9007199254740991},"notes":{"type":"integer","minimum":0,"maximum":9007199254740991},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetches":{"type":"integer","minimum":0,"maximum":9007199254740991},"bytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"routes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"integer","minimum":0,"maximum":9007199254740991}},"status_2xx":{"type":"integer","minimum":0,"maximum":9007199254740991},"status_4xx":{"type":"integer","minimum":0,"maximum":9007199254740991},"status_5xx":{"type":"integer","minimum":0,"maximum":9007199254740991},"fail_routes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"status":{"type":"integer","minimum":400,"maximum":599},"count":{"type":"integer","minimum":0,"maximum":9007199254740991},"last_code":{"type":"string"},"last_fix":{"type":"string"}},"required":["status","count"],"additionalProperties":false},"description":"Small map of `<route>:<status>` to {status, count} for the most common failures."}},"required":["key_id","day","requests","runs","notes","reads","fetches","bytes","status_2xx","status_4xx","status_5xx","fail_routes"],"additionalProperties":false}},"quotas":{"type":"object","properties":{"requests":{"type":"integer","minimum":0,"maximum":9007199254740991},"runs":{"type":"integer","minimum":0,"maximum":9007199254740991},"notes":{"type":"integer","minimum":0,"maximum":9007199254740991},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991},"fetches":{"type":"integer","minimum":0,"maximum":9007199254740991},"bytes":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["requests","runs","notes","fetches","bytes"],"additionalProperties":false}},"required":["product","version","exported_at","runs","notes","usage","quotas"],"additionalProperties":false},"ExportJob":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","const":"ready"},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"expires_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"download":{"type":"string"},"filename":{"type":"string"},"byte_length":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["id","status","created_at","expires_at","download","filename","byte_length"],"additionalProperties":false},"WorkspaceStatus":{"type":"object","properties":{"id":{"type":"string"},"plan":{"type":"string"},"created_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"deleted_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"cancel_until":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"billing_status":{"type":"string"},"overage":{"type":"boolean"},"spend_cap_usd":{"type":["number","null"]},"alert":{"type":"object","properties":{"webhook_url":{"type":"string"},"email":{"type":"string"},"webhook_configured":{"type":"boolean"}},"required":["webhook_configured"],"additionalProperties":false}},"required":["id","plan","created_at"],"additionalProperties":false},"WorkspaceDelete":{"type":"object","properties":{"id":{"type":"string"},"deleted_at":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"cancel_until":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T","description":"ISO-8601 datetime"},"cancel_days":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","deleted_at","cancel_until","cancel_days"],"additionalProperties":false}}}}