{"openapi":"3.1.0","info":{"title":"Ricord API","version":"1.0.0","summary":"Persistent memory for AI agents.","description":"Save, search, and reason over knowledge for any AI agent. Works with Claude Code, Cursor, Windsurf, or any MCP client, and is directly callable from your own code.\n\nAll endpoints require an API key. Generate one at https://ricord.ai/dashboard. Send it as `Authorization: Bearer <key>`.\n\nEndpoints tagged **Beta** have stable behavior but their response shape may change before 1.0.","contact":{"name":"Ricord","url":"https://ricord.ai"}},"servers":[{"url":"https://api.ricord.ai","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Auth","description":"API keys and introspection."},{"name":"Ingest","description":"Write knowledge into Ricord."},{"name":"Recall","description":"Search and reason over knowledge."},{"name":"Memories","description":"Manage individual facts, preferences, and decisions."},{"name":"Usage","description":"Credit balance and usage history."},{"name":"Knowledge Graph (Beta)","description":"Auto-generated wiki pages, graph, and knowledge intelligence. Response shapes may evolve before 1.0."},{"name":"System","description":"Health and spec discovery. No auth."},{"name":"Teams","description":"Collaboration — shared knowledge, invites, reviews"},{"name":"Billing","description":"Stripe integration and subscription management"},{"name":"Models","description":"Available LLM models and pricing"},{"name":"Data","description":"Export and import your data"},{"name":"Assets","description":"Multimodal uploads"},{"name":"Embeddings","description":"Direct embedding generation"},{"name":"Integrations","description":"Composio-powered integrations (Gmail, Slack, Notion, …)"},{"name":"Procedures","description":"Saved SOPs, playbooks, and prompts the agent can run on trigger."}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Ricord API key (rc_live_...)"}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["type","message"],"properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","billing_error","not_found_error","rate_limit_error","upstream_error","internal_error"]},"message":{"type":"string"},"code":{"type":"string"},"param":{"type":["string","null"]}}}}},"Message":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","enum":["user","assistant","system"]},"content":{"type":"string"}}},"Memory":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"content":{"type":"string"},"type":{"type":"string","description":"Kind of memory. Step-by-step SOPs (`procedure`/`playbook`) are stored separately under `/v1/procedures` and are not valid here. Conversation episodes are stored under `/v1/ingest/conversations`.","enum":["fact","preference","decision","reference","anti-pattern","observation"]},"tags":{"type":"array","items":{"type":"string"}},"confidence":{"type":"number","minimum":0,"maximum":1},"title":{"type":"string"},"summary":{"type":"string"},"created_at":{"type":"integer","format":"int64","description":"Unix epoch milliseconds"},"updated_at":{"type":"integer","format":"int64"}}},"SearchResult":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"content":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"score":{"type":"number"},"kind":{"type":"string"}}},"Procedure":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["sop","playbook","ui_walkthrough","prompt"]},"title":{"type":"string"},"trigger":{"type":["string","null"]},"guards":{"type":"array","items":{"type":"string"}},"steps":{"type":"array","items":{"type":"object"}},"body":{"type":"string"},"confidence":{"type":"number","minimum":0,"maximum":1},"created_at":{"type":"integer","format":"int64","description":"Unix epoch milliseconds"},"updated_at":{"type":"integer","format":"int64"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"BadRequest":{"description":"Invalid request body or parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded. See `X-RateLimit-Reset` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InsufficientCredits":{"description":"Not enough credits for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/health":{"get":{"tags":["System"],"summary":"Health check","description":"Liveness + dependency-health probe. Returns `{status, buildVersion, database, providers}` — 200 when both DB and at least one LLM provider key are healthy, 503 when degraded. No auth required.","security":[],"responses":{"200":{"description":"Service is reachable."}}}},"/v1/assets":{"get":{"summary":"List uploaded assets","tags":["Assets"],"responses":{"200":{"description":"Assets"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"summary":"Upload an asset (50MB max)","tags":["Assets"],"responses":{"200":{"description":"Uploaded"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/composio/connections":{"get":{"summary":"List your active integration connections","tags":["Integrations"],"responses":{"200":{"description":"Success"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/composio/execute":{"post":{"summary":"Execute a tool on a connected integration (20 credits)","tags":["Integrations"],"responses":{"200":{"description":"Success"},"429":{"$ref":"#/components/responses/RateLimited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["toolkit","action"],"properties":{"toolkit":{"type":"string"},"action":{"type":"string"},"arguments":{"type":"object"}}}}}}}},"/v1/composio/toolkits":{"get":{"summary":"List available integration toolkits (Gmail, Slack, Notion, …)","tags":["Integrations"],"responses":{"200":{"description":"Success"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/embeddings":{"post":{"summary":"Generate a text embedding","description":"Generate a 768-dim text embedding for use in your own retrieval pipeline. Same model as the one used internally for Ricord recall, so vectors are comparable across systems.","tags":["Embeddings"],"responses":{"200":{"description":"Success"},"429":{"$ref":"#/components/responses/RateLimited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["input"],"properties":{"input":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]}}}}}}}},"/v1/export":{"get":{"summary":"Export all your data as JSON (0 credits)","description":"Stream a complete GDPR-ready JSON dump of the caller's entire account: memories, KB pages, procedures, preferences, edges, and aliases. Zero credits. Use `/v1/import` to restore.","tags":["Data"],"responses":{"200":{"description":"Success"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/import":{"post":{"summary":"Import from a prior export (0 credits)","description":"Restore from a `/v1/export` payload. Idempotent on memory ids — duplicates are skipped. Embeddings are regenerated on import; expect a few seconds per 100 items.\n\n**Dashboard-only**: this endpoint accepts only the Firebase session cookie set after a web login. API-key callers receive `403 dashboard_only` — mass data writes are restricted to UI to prevent accidental account overwrites. For programmatic backup, use `GET /v1/export`.","tags":["Data"],"responses":{"200":{"description":"Success"},"403":{"description":"API-key callers blocked (`error.code: dashboard_only`). Use the web dashboard for imports.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}}}}}}}},"/v1/ingest/conversations":{"post":{"tags":["Ingest"],"summary":"Ingest a conversation or document","description":"The primary ingest endpoint. Accepts a sequence of messages or a single document. Runs the full pipeline: turn extraction, fact extraction, knowledge graph update, and wiki page generation.\n\nSet `queueing: true` for large payloads and poll `GET /v1/ingest/conversations/status/{id}`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}},"session_id":{"type":"string","description":"Groups related turns. Defaults to a new session."},"async_mode":{"type":"boolean","default":false},"metadata":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"Ingest accepted.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["completed","queued"]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-canonical":true}},"/v1/ingest/conversations/status/{id}":{"get":{"tags":["Ingest"],"summary":"Poll an async ingest job","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Current job status.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["queued","processing","completed","failed"]},"searchable":{"type":"boolean"},"error":{"type":"string"}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-alias-of":"/v1/ingest/status/{id}"}},"/v1/ingest/source":{"post":{"tags":["Ingest"],"summary":"Ingest any source type","description":"Auto-detects and extracts text from any of 8 modalities: plain text, URL, YouTube video, PDF, DOCX, image (OCR + captions), audio (transcription), or video. Uploaded content is processed then ingested through the same pipeline as `/v1/ingest/conversations`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["source"],"properties":{"source":{"type":"string","description":"A URL, raw text, or a data URL containing the content."},"type":{"type":"string","enum":["auto","text","url","youtube","pdf","docx","image","audio","video"],"default":"auto"},"session_id":{"type":"string"},"metadata":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"Content extracted and ingested."},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/ingest/status/{id}":{"get":{"tags":["Ingest"],"summary":"Poll an async ingest job","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Current job status.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["queued","processing","completed","failed"]},"searchable":{"type":"boolean"},"error":{"type":"string"}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-canonical":true}},"/v1/kb/graph":{"get":{"tags":["Knowledge Graph (Beta)"],"summary":"Get the knowledge graph","description":"Returns nodes (wiki pages) and edges (relationships) in a shape ready for visualization. Suitable for building your own graph UI.","responses":{"200":{"description":"Graph JSON."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/kb/pages":{"get":{"tags":["Knowledge Graph (Beta)"],"summary":"List your auto-generated wiki pages","description":"Ricord automatically builds wiki pages from the entities it finds in your ingested content. This lists them.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":50}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Wiki page list."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/kb/pages/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Knowledge Graph (Beta)"],"summary":"Fetch a single wiki page","responses":{"200":{"description":"Page content, cross-references, and metadata."},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/kb/tasks":{"get":{"tags":["Knowledge Graph (Beta)"],"summary":"List extracted tasks","description":"Ricord extracts TODO-like tasks from your ingested content. This lists them with open/done filtering.","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["open","done","all"],"default":"open"}}],"responses":{"200":{"description":"Task list."},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Knowledge Graph (Beta)"],"summary":"Create a task","description":"Manually create a KB task. Tasks are usually auto-extracted from ingested text; this endpoint exists for dashboard quick-add and import flows.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string"},"status":{"type":"string","enum":["open","done"]},"linked_page_id":{"type":"string"},"due_at":{"type":"string","format":"date-time"}}}}}},"responses":{"200":{"description":"Created task."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/key":{"get":{"tags":["Auth"],"summary":"Introspect the calling key","description":"Returns tier, credit balance, and permissions for the API key in the Authorization header. Useful as a first call to verify credentials work.","responses":{"200":{"description":"Key is valid.","content":{"application/json":{"schema":{"type":"object","properties":{"tier":{"type":"string","example":"pro"},"balance":{"type":"integer","description":"Remaining credits."},"permissions":{"type":"array","items":{"type":"string"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/keys":{"get":{"tags":["Auth"],"summary":"List your API keys","description":"List the caller's API keys. Returns metadata only (key prefix, created_at, last_used_at) — secret bodies are never returned after creation.","responses":{"200":{"description":"Array of keys belonging to the authenticated user."},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Auth"],"summary":"Create a new API key","description":"Generate a new `rc_live_*` API key. The full key value is returned ONCE in this response; subsequent GETs only show the prefix. Store it somewhere safe.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","example":"production"},"permissions":{"type":"array","items":{"type":"string"},"example":["all"]}}}}}},"responses":{"200":{"description":"Newly-minted key. Only shown once — store it immediately."},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/keys/{id}":{"delete":{"tags":["Auth"],"summary":"Revoke a key","description":"Revoke an API key by id. Takes effect on the next request — there is no grace period. In-flight requests using the key may complete.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Key revoked."},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/memories":{"post":{"tags":["Memories"],"summary":"Save a memory","description":"Create a new atomic memory — a fact, preference, decision, reference, anti-pattern, or observation. Returns the canonical `id` you can use with `GET /v1/memories/{id}` or `DELETE /v1/memories/{id}`. The content is embedded asynchronously; the response `status` will be `searchable` when the memory is fully indexed.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string","description":"The memory text. Be specific and concrete — this is what gets embedded for retrieval."},"type":{"$ref":"#/components/schemas/Memory/properties/type"},"title":{"type":"string","description":"Optional short title. Defaults to the first ~80 chars of content."},"summary":{"type":"string","description":"Optional summary line shown on memory cards."},"tags":{"type":"array","items":{"type":"string"},"description":"Free-form tags for filtering. `pref:*` tags are auto-extracted for preference kinds."},"confidence":{"type":"number","minimum":0,"maximum":1,"default":0.9,"description":"How confident you are in this memory (used for ranking on recall)."},"project_id":{"type":"string","description":"Optional project scope. Defaults to the caller's auto-detected project."},"team_id":{"type":"string","format":"uuid","description":"Optional team scope. Memory will be visible to every team member with role >= member."}}}}}},"responses":{"201":{"description":"Memory created","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"$ref":"#/components/schemas/Memory/properties/type"},"content":{"type":"string"},"title":{"type":"string"},"status":{"type":"string","enum":["searchable","embedding"],"description":"`embedding` while the async indexer runs; `searchable` once it's complete."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Insufficient credits — `error.code: insufficient_credits`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"get":{"tags":["Memories"],"summary":"List your memories","description":"List stored memories with optional filters by `kind`, `tag`, `project_id`, or `team_id`. Paginates via `limit` + `cursor`. Newest first.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":200}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}},{"name":"type","in":"query","schema":{"type":"string"},"description":"Filter by memory type."}],"responses":{"200":{"description":"Page of memories.","content":{"application/json":{"schema":{"type":"object","properties":{"articles":{"type":"array","items":{"$ref":"#/components/schemas/Memory"}},"count":{"type":"integer"},"next_cursor":{"type":["string","null"]}}}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/memories/fact":{"post":{"tags":["Memories"],"summary":"Save a single fact","description":"Stores one piece of knowledge directly — no conversation, no pipeline. Useful for manually curated memories, preferences, or corrections.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string"},"type":{"type":"string","enum":["fact","preference","procedure","decision","reference","playbook","anti-pattern","episode","observation"],"default":"fact"},"tags":{"type":"array","items":{"type":"string"}},"title":{"type":"string"},"confidence":{"type":"number","minimum":0,"maximum":1,"default":0.9}}}}}},"responses":{"200":{"description":"Fact saved.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/memories/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Memories"],"summary":"Fetch one memory","description":"Fetch a single memory by id. Returns the full content, kind, tags, embedding status, and any linked KB pages.","responses":{"200":{"description":"Memory.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Memory"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"put":{"tags":["Memories"],"summary":"Update a memory","description":"Replaces content (and optionally tags/confidence). The memory is re-embedded on the next search.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"confidence":{"type":"number"},"change_summary":{"type":"string","description":"Optional note describing why the memory changed."}}}}}},"responses":{"200":{"description":"Updated."},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Memories"],"summary":"Delete a memory","description":"Hard-delete a memory by id — removes the row, embedding, and any pending ingest jobs. Cannot be undone. Call `/v1/export` first if you need a backup.","responses":{"204":{"description":"Deleted."},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/memories/{id}/feedback":{"post":{"tags":["Memories"],"summary":"Thumbs-up or thumbs-down a memory","description":"Positive feedback raises the memory's confidence and ranks it higher in future searches. Negative feedback deprioritizes it.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["outcome"],"properties":{"outcome":{"type":"string","enum":["correct","incorrect"]}}}}}},"responses":{"200":{"description":"Feedback recorded."},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/models":{"get":{"summary":"List available models with pricing","description":"Catalog of every model routable through the proxy with provider, context window, and price-per-million-tokens. Use the `id` field as the value of `model` on `/v1/chat/completions`.","tags":["Models"],"responses":{"200":{"description":"Success"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/models/{id}":{"get":{"summary":"Get one model","description":"Fetch a single model's pricing and capabilities by id.","tags":["Models"],"responses":{"200":{"description":"Success"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}]}},"/v1/search":{"post":{"tags":["Recall"],"summary":"Search your knowledge","description":"Hybrid retrieval across everything you've ingested. Returns the top-K most relevant pieces of knowledge along with a pre-assembled `context` string you can drop into an LLM prompt.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"type":"string","example":"What's my VS Code setup?"},"limit":{"type":"integer","minimum":1,"maximum":50,"default":10},"uid":{"type":"string","description":"Optional sub-tenant identifier."}}}}}},"responses":{"200":{"description":"Search results plus an LLM-ready `context` string.","content":{"application/json":{"schema":{"type":"object","properties":{"context":{"type":"string"},"context_length":{"type":"integer"},"results":{"type":"array","items":{"$ref":"#/components/schemas/SearchResult"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/stripe/checkout":{"post":{"summary":"Create a Stripe Checkout session","description":"Start a Stripe-hosted Checkout session for a plan upgrade. Returns `{url}` — redirect the user there to complete payment.","tags":["Billing"],"responses":{"200":{"description":"Success"},"403":{"description":"API-key callers blocked (`error.code: dashboard_only`). Stripe management is web-UI-only.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["priceId","successUrl","cancelUrl"],"properties":{"priceId":{"type":"string"},"successUrl":{"type":"string","format":"uri"},"cancelUrl":{"type":"string","format":"uri"}}}}}}}},"/v1/stripe/portal":{"post":{"summary":"Create a Stripe Customer Portal session","description":"Start a Stripe Customer Portal session for the caller to manage their subscription, change payment method, or cancel. Returns `{url}`.","tags":["Billing"],"responses":{"200":{"description":"Success"},"403":{"description":"API-key callers blocked (`error.code: dashboard_only`). Stripe management is web-UI-only.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/stripe/subscription":{"get":{"summary":"Get your current subscription status","description":"Returns the caller's current subscription state: plan, status (active/trialing/past_due/canceled), trial_end, current_period_end, and Stripe customer id.","tags":["Billing"],"responses":{"200":{"description":"Success"},"403":{"description":"API-key callers blocked (`error.code: dashboard_only`). Stripe management is web-UI-only.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/teams/search":{"get":{"summary":"Search across every team you belong to","description":"Cross-team merged search — runs a semantic query against every team the caller is a member of, in a single request. Use the team-specific `/v1/teams/{id}/search` for a single team. Requires a Firebase ID token (API key auth is not accepted on team management routes).","tags":["Teams"],"parameters":[{"name":"q","in":"query","required":true,"description":"Free-text query.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Max hits to return (default 20, max 100).","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"Ranked hits across all teams"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/teams":{"get":{"summary":"List teams you belong to","tags":["Teams"],"responses":{"200":{"description":"List of teams"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"summary":"Create a team","tags":["Teams"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string"},"name":{"type":"string"}}}}}},"responses":{"200":{"description":"Created team"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/teams/{id}":{"get":{"summary":"Get team details","tags":["Teams"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Team detail"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"summary":"Delete a team (owner only)","tags":["Teams"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/teams/{id}/invites":{"get":{"summary":"List pending invites","tags":["Teams"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Invites"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"summary":"Invite someone by email","tags":["Teams"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","role"],"properties":{"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["owner","admin","reviewer","member","viewer"]}}}}}},"responses":{"200":{"description":"Invite created"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/teams/{id}/members":{"get":{"summary":"List team members","tags":["Teams"],"responses":{"200":{"description":"Success"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}]}},"/v1/teams/{id}/search":{"get":{"summary":"Search the team knowledge pool","tags":["Teams"],"responses":{"200":{"description":"Success"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"query","in":"query","required":true,"schema":{"type":"string"}}]}},"/v1/teams/{id}/shares":{"get":{"summary":"List team shares","tags":["Teams"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Shares"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"summary":"Share a fact or wiki page with the team","tags":["Teams"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["object_type","source_id"],"properties":{"object_type":{"type":"string","enum":["fact","kb_page","instruction","bundle","space"]},"source_id":{"type":"string","description":"ID of the wiki/fact/page to share"}}}}}},"responses":{"200":{"description":"Share created (status=pending until reviewed)"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/usage":{"get":{"tags":["Usage"],"summary":"Current credit balance and this-period usage","responses":{"200":{"description":"Usage object with `balance`, `tier`, and current-period counters."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/usage/summary":{"get":{"tags":["Usage"],"summary":"Usage rolled up by service","responses":{"200":{"description":"Per-service credit totals for the current billing period."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/kb/lint":{"post":{"tags":["Knowledge Graph (Beta)"],"summary":"Lint the knowledge base","description":"Surface orphaned, stale, oversized, or duplicate wiki pages plus broken wiki-links. Returns a structured list of issues for the dashboard or a CI check.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"checks":{"type":"array","items":{"type":"string","enum":["orphan","stale","oversize","duplicate","broken-wikilink"]}}}}}}},"responses":{"200":{"description":"List of lint findings."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/kb/contradictions":{"get":{"tags":["Knowledge Graph (Beta)"],"summary":"List contradictions","description":"Pairs of conflicting claims surfaced by the rollup pipeline. Filter by status (`open`, `resolved`, `dismissed`).","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["open","resolved","dismissed","all"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Array of contradictions."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/kb/boot-context":{"get":{"tags":["Knowledge Graph (Beta)"],"summary":"Boot context","description":"One-shot synthesized context to prime an agent at session start: identity facts, current focus, top knowledge-graph communities. Designed to be the first call a new agent makes.","responses":{"200":{"description":"Boot-context bundle."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/kb/export":{"get":{"tags":["Knowledge Graph (Beta)"],"summary":"Export knowledge base","description":"Stream the full KB as a portable archive (JSON or Markdown). Used by the dashboard \"Export\" button and GDPR data-portability flows.","parameters":[{"name":"format","in":"query","schema":{"type":"string","enum":["json","markdown","obsidian"]}}],"responses":{"200":{"description":"Archive stream."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/kb/graph/snapshot":{"get":{"tags":["Knowledge Graph (Beta)"],"summary":"Graph snapshot","description":"Nodes + edges suitable for client-side rendering (dashboard graph view).","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":5000}}],"responses":{"200":{"description":"Snapshot { nodes[], edges[] }."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/kb/graph/entities":{"get":{"tags":["Knowledge Graph (Beta)"],"summary":"List graph entities","description":"All entity nodes with degree counts.","parameters":[{"name":"q","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":1000}}],"responses":{"200":{"description":"Entity list."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/kb/graph/entity/{name}":{"get":{"tags":["Knowledge Graph (Beta)"],"summary":"Entity detail","description":"Neighbors and source articles for one entity.","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Entity + neighbors + sources."},"404":{"description":"Entity not found."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/kb/graph/neighborhood":{"get":{"tags":["Knowledge Graph (Beta)"],"summary":"Neighborhood walk","description":"K-hop neighborhood around a seed entity for graph-augmented retrieval previews.","parameters":[{"name":"entity","in":"query","required":true,"schema":{"type":"string"}},{"name":"hops","in":"query","schema":{"type":"integer","minimum":1,"maximum":3}}],"responses":{"200":{"description":"Subgraph."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/kb/tasks/{id}":{"patch":{"tags":["Knowledge Graph (Beta)"],"summary":"Update a task","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"status":{"type":"string","enum":["open","done"]},"linked_page_id":{"type":"string"},"due_at":{"type":"string","format":"date-time"}}}}}},"responses":{"200":{"description":"Updated task."},"404":{"description":"Task not found."},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Knowledge Graph (Beta)"],"summary":"Delete a task","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"404":{"description":"Task not found."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/procedures":{"get":{"tags":["Procedures"],"summary":"List your procedures","description":"List saved procedures (SOPs, playbooks, ui_walkthroughs, prompts). Filter by `kind` query param. Newest first.","parameters":[{"name":"kind","in":"query","required":false,"schema":{"type":"string","enum":["sop","playbook","ui_walkthrough","prompt"]}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"maximum":200}}],"responses":{"200":{"description":"Procedure list","content":{"application/json":{"schema":{"type":"object","properties":{"procedures":{"type":"array","items":{"$ref":"#/components/schemas/Procedure"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Procedures"],"summary":"Save a procedure","description":"Create a procedure the agent can later run via `ricord_run_procedure`. SOPs / playbooks / ui_walkthroughs require `trigger` + `steps`; `prompt` kind requires `body` instead. Accepts both `title` / `name` and `procedure_kind` / `kind` as field aliases.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","description":"Short title (also accepted as `name` alias)."},"procedure_kind":{"type":"string","enum":["sop","playbook","ui_walkthrough","prompt"],"default":"sop","description":"Also accepted as `kind` alias."},"trigger":{"type":"string","description":"Phrase or pattern that should fire this procedure. Required for sop/playbook/ui_walkthrough."},"steps":{"type":"array","items":{"type":"object","required":["action"],"properties":{"action":{"type":"string"}}},"description":"Step-by-step instructions. Each step must have `action` (not `text`). Required for sop/playbook/ui_walkthrough."},"guards":{"type":"array","items":{"type":"string"},"description":"Hard preconditions / rules that must hold at runtime."},"body":{"type":"string","description":"Prompt template content. Required for `prompt` kind."},"confidence":{"type":"number","minimum":0,"maximum":1,"default":0.7}}}}}},"responses":{"201":{"description":"Procedure created","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/procedures/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Procedures"],"summary":"Fetch one procedure","description":"Returns the full procedure record including steps, guards, trigger, and metadata.","responses":{"200":{"description":"Procedure"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"put":{"tags":["Procedures"],"summary":"Update a procedure","description":"Replace fields on an existing procedure. Same body shape as POST, but every field is optional — unset fields preserve the prior value.","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Updated procedure"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Procedures"],"summary":"Delete a procedure","description":"Hard-delete a procedure by id. Cannot be undone.","responses":{"204":{"description":"Deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/extraction/prompt":{"get":{"tags":["Ingest"],"summary":"Fetch the client-side extraction prompt","description":"Returns the canonical anchor-extraction prompt that your host LLM should run before POSTing structured anchors, connections, and tasks to `/v1/ingest/extracted`. Server runs zero generative LLM for knowledge extraction — your client owns it.\n\nPass `?project_id=...` to scope the response: when supplied, the prompt is augmented with the list of existing topic slugs in that project so the LLM prefers established names over inventing new ones.","parameters":[{"name":"project_id","in":"query","required":false,"schema":{"type":"string"},"description":"Optional project ID. When supplied, existing topic slugs are appended to the prompt so the LLM reuses them."}],"responses":{"200":{"description":"Extraction prompt, output schema, and request caps.","content":{"application/json":{"schema":{"type":"object","required":["schema_version","prompt","output_schema","submit_url","caps"],"properties":{"schema_version":{"type":"integer","description":"Incremented when the prompt or output_schema shape changes incompatibly. Pin this in your client and re-fetch when it changes."},"prompt":{"type":"string","description":"System-level prompt to inject before the conversation you want extracted. Output from your LLM must conform to `output_schema`."},"output_schema":{"type":"object","description":"JSON-Schema-ish description of the payload your client must POST to `submit_url`."},"submit_url":{"type":"string","description":"Path your client should POST the extracted payload to (currently `/v1/ingest/extracted`)."},"caps":{"type":"object","description":"Per-request limits enforced by `/v1/ingest/extracted`. Validate locally before submission.","properties":{"max_messages":{"type":"integer"},"max_total_chars":{"type":"integer"},"max_anchors":{"type":"integer"},"max_connections":{"type":"integer"},"max_tasks":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/ingest/extracted":{"post":{"tags":["Ingest"],"summary":"Submit pre-extracted anchors, connections, and tasks","description":"Canonical client-driven KG write path. The caller's host LLM runs the extraction prompt (fetch it from `/v1/extraction/prompt`) locally, then POSTs the structured result here. Server runs ZERO generative LLM calls — only embeds raw turn text and writes storage rows.\n\n**For conversations only** (chat turns with anchors). Repo wikis use `ricord build` + `ricord push` (the dashboard's Wikis tab).\n\nCaps: 200 messages, 200 KB total content, 10 anchors, 30 connections, 5 tasks. Connection endpoints must reference declared anchors.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["messages","extraction_meta"],"properties":{"messages":{"type":"array","maxItems":200,"items":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","enum":["user","assistant","system"]},"content":{"type":"string","minLength":1},"timestamp":{"type":"string","description":"Optional ISO-8601 timestamp."}}}},"anchors":{"type":"array","maxItems":10,"items":{"type":"object","required":["type","value","confidence"],"properties":{"type":{"type":"string","enum":["entity","topic","epoch"]},"subtype":{"type":"string","maxLength":40},"value":{"type":"string","minLength":1,"maxLength":80},"aliases":{"type":"array","items":{"type":"string","maxLength":120},"maxItems":5},"confidence":{"type":"number","minimum":0,"maximum":1}}}},"connections":{"type":"array","maxItems":30,"items":{"type":"object","required":["source","target","relation","strength"],"properties":{"source":{"type":"string","description":"Anchor value the connection starts from (must match a declared anchor)."},"target":{"type":"string","description":"Anchor value the connection points to (must match a declared anchor)."},"relation":{"type":"string","enum":["mentions","relates_to","supersedes","contradicts","caused_by","part_of","derived_from"]},"relationship":{"type":"string","maxLength":200,"description":"Free-form qualifier shown on graph edges."},"strength":{"type":"number","minimum":0,"maximum":1},"valid_at":{"type":["string","number","null"]},"invalid_at":{"type":["string","number","null"]}}}},"tasks":{"type":"array","maxItems":5,"items":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"due_at":{"type":["string","null"]}}}},"extraction_meta":{"type":"object","required":["model","client"],"properties":{"model":{"type":"string","description":"LLM model id used for extraction (e.g. `claude-opus-4-7`, `gpt-4o`)."},"client":{"type":"string","description":"Calling client identifier (`claude-code`, `cursor`, custom name, …)."},"schema_version":{"type":"integer","description":"Extraction-prompt schema version returned by `/v1/extraction/prompt`."}}},"session_id":{"type":"string","description":"Optional session identifier to group related ingests."}}}}}},"responses":{"200":{"description":"Anchors / connections / tasks accepted","content":{"application/json":{"schema":{"type":"object","properties":{"message_id":{"type":"string","format":"uuid"},"anchors_written":{"type":"integer"},"connections_written":{"type":"integer"},"tasks_written":{"type":"integer"},"pages_needing_rollup":{"type":"array","items":{"type":"string","format":"uuid"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Insufficient credits — `error.code: insufficient_credits`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/credits/ledger":{"get":{"tags":["Billing"],"summary":"Credit ledger entries","description":"Append-only ledger of every credit-affecting event on the caller's account — deposits, charges, refunds, monthly allotments. Use to power automated billing dashboards, low-balance alerts, or cost-attribution reports. Newest first.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"maximum":200}},{"name":"since","in":"query","required":false,"schema":{"type":"string"},"description":"ISO-8601 timestamp; only entries newer than this are returned."}],"responses":{"200":{"description":"Ledger entries","content":{"application/json":{"schema":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"service":{"type":"string","description":"What the credit was spent on (memories_fact, search, embeddings, proxy_chat, etc.)"},"credits":{"type":"integer","description":"Negative = charge, positive = deposit/refund"},"balance_after":{"type":"integer"},"created_at":{"type":"integer","format":"int64","description":"Unix epoch milliseconds"}}}},"total":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}