{"openapi":"3.1.0","info":{"title":"SkimmerDB API","version":"0.1.0","description":"SkimmerDB is a hosted search database for apps and AI agents: make a namespace per user, task or project, write documents, and find them by exact text or regex with metadata filters, count them exactly, search them by vector (alone or hybrid), or query their metadata with SQL (beta). Every namespace lives in object storage: an idle one costs only the bytes it stores.\n\n**Start.** `POST /v1/signup` makes your account and answers with a full-access API key (shown once). Write documents to a namespace name (the first write makes it; the answer says `created: true`), then search. The API is at `https://api.skimmerdb.com/v1`; the older `https://skimmerdb.com/api/...` paths are the same operations, deprecated.\n\n**Modeling.** One query reads one namespace: put together what you query together (events you aggregate across users go in one namespace, with `user` as metadata), split what you'd delete or scope a key to (`mem-<user>`). `text` is what `q` searches; `meta` is what filters and SQL read: keep it flat (top-level scalar keys), one type per key, times as integer seconds. Use stable, meaningful ids so retries replace rather than duplicate. Guide: https://skimmerdb.com/docs#data-model. Agent skill (Claude Code, Codex): https://skimmerdb.com/skills/skimmerdb/SKILL.md.\n\n**Auth.** Send an API key as `Authorization: Bearer tb_...` (or `X-API-Key: tb_...`). A key has a scope: read (list namespaces, search, count, get documents, warm, SQL, usage) < write (also create and delete namespaces, write, update, delete and import documents) < admin (also keys and billing). Only an admin key without namespace limits or caps, or the owner's login (`POST /v1/login`), can make, list, change or revoke keys, so a read or write key given to an app or agent can't make more. A key may be limited to some namespaces (names or `prefix*`) and capped.\n\n**Errors** have one shape whatever the status: `{\"error\": \"a sentence\", \"code\": \"a_word\"}`, plus `fields` (invalid input: field -> what's wrong) or `retry_after` when they apply. 400 malformed, 401 no or bad key, 402 the free plan's usage or namespace count is used up, 403 not allowed, 404 not found, 409 exists, 413 too big, 422 invalid (says why), 429 over a rate, concurrency or cap (`Retry-After` says when to retry), 503 object storage failed (nothing was written: retry), 507 storage quota full.\n\n**Rate limits** are per tenant and per key, per server: requests a second (free 100, pro 1,000) and requests in flight (free 16, pro 64). Rate-limited answers (namespaces, documents, search, SQL, MCP) carry `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` and `RateLimit-Policy` (IETF draft 07); past a limit the answer is 429 with `Retry-After`.\n\n**Metering.** Every search, count, document read, warm and SQL query is one query; writes are metered as documents and bytes written; storage as GB-months. Operations carry `x-metered: query | write`. Every query's answer says how warm the namespace was (`temperature`) and what it read from object storage (`store_gets`, `store_bytes`). `GET /v1/usage` shows the period so far and its bill.","contact":{"url":"https://skimmerdb.com"}},"servers":[{"url":"https://api.skimmerdb.com","description":"SkimmerDB"},{"url":"http://127.0.0.1:8080","description":"A local server"}],"tags":[{"name":"Accounts","description":"Sign up, log in, who you are."},{"name":"Keys","description":"API keys: scopes, namespaces, caps."},{"name":"Tenants","description":"A tenant owns namespaces, keys, usage and a bill."},{"name":"Namespaces","description":"Named sets of documents, one per user, task or project."},{"name":"Documents","description":"Write, update, delete, read, import."},{"name":"Search","description":"Text, regex, filters, vectors, counts."},{"name":"Embeddings","description":"Documents' text and semantic searches embedded with your own OpenAI, Voyage, Gemini or Cohere key, or hosted by SkimmerDB at cost."},{"name":"SQL (beta)","description":"SQL over the documents' metadata."},{"name":"Usage","description":"What you used and what it costs."},{"name":"Billing","description":"Plans and Stripe."},{"name":"Linked namespaces","description":"Indexes linked by reference."},{"name":"MCP","description":"For AI agents."},{"name":"Meta","description":"Health and this document."}],"paths":{"/healthz":{"get":{"operationId":"health","summary":"Health check","tags":["Meta"],"responses":{"200":{"description":"OK","content":{"text/plain":{"schema":{"type":"string"},"example":"ok"}}}},"security":[]}},"/openapi.json":{"get":{"operationId":"getOpenapi","summary":"This document (OpenAPI 3.1, JSON)","tags":["Meta"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}},"security":[]}},"/v1/signup":{"post":{"operationId":"signup","summary":"Sign up","tags":["Accounts"],"description":"Makes a person, their first tenant and its first API key (full access, scope admin): the answer's key is ready to use and shown only here. Make narrower keys (read or write, some namespaces, caps) for apps and agents with POST /v1/keys.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupRequest"},"examples":{"signup":{"summary":"A new account","value":{"email": "ada@example.com", "name": "Ada", "password": "correct horse"}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Signup"},"example":{"id": 42, "tenant_id": 42, "key": "tb_3q2-Xw9Lk2bFz0aQvP1mYc7RnT4sUe8d", "key_id": 7, "key_prefix": "tb_3q2-Xw9", "scope": "admin"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"}},"security":[]}},"/v1/login":{"post":{"operationId":"login","summary":"Log in","tags":["Accounts"],"description":"A login token (JWT, valid a day) for managing your account: keys, tenants, billing. It acts for your default tenant, or the one named by X-Tenant. Not needed to start: signup's answer has a key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"},"examples":{"login":{"summary":"Log in","value":{"email": "ada@example.com", "password": "correct horse"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Token"},"example":{"token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiJ9.c2lnbmF0dXJl"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}},"security":[]}},"/v1/email/verify":{"post":{"operationId":"verifyEmail","summary":"Verify an email","tags":["Accounts"],"description":"Uses the link a signup's email carries (single use, 7 days; skimmerdb.com/verify-email sends it here). Until an owner's email is verified a free tenant gets 1% of the free plan (402 email_unverified past it); after, all of it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"From the emailed link (its token= part)."}},"required":["token"]},"examples":{"verify":{"summary":"The emailed link's token","value":{"token": "q7Xc2bN0rT5mVw8sKe1dZy4uHf6jLa3p"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"verified":{"type":"boolean"}},"required":["verified"]},"example":{"verified": true}}}},"400":{"$ref":"#/components/responses/BadRequest"}},"security":[]}},"/v1/email/verify/resend":{"post":{"operationId":"resendVerification","summary":"Send a new verification link","tags":["Accounts"],"description":"Emails the signed-in person (or the key's person) a new link, at most once a minute. verified: true when there is nothing to verify.","responses":{"202":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResendResult"},"example":{"verified": false, "sent": true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/password/forgot":{"post":{"operationId":"forgotPassword","summary":"Ask for a password reset link","tags":["Accounts"],"description":"Emails a reset link (single use, an hour) to the address if it has an account. The answer is the same either way.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"}},"required":["email"]},"examples":{"forgot":{"summary":"An account's email","value":{"email": "ada@example.com"}}}}}},"responses":{"202":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"sent":{"type":"boolean"}},"required":["sent"]},"example":{"sent": true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[]}},"/v1/password/reset":{"post":{"operationId":"resetPassword","summary":"Set a new password","tags":["Accounts"],"description":"Uses a reset link's token (once) to set the password; it also verifies the email, and the account's other reset links stop working.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"password":{"type":"string","minLength":8,"maxLength":200}},"required":["token","password"]},"examples":{"reset":{"summary":"A new password","value":{"token": "q7Xc2bN0rT5mVw8sKe1dZy4uHf6jLa3p", "password": "a better horse"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"reset":{"type":"boolean"}},"required":["reset"]},"example":{"reset": true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"422":{"$ref":"#/components/responses/Unprocessable"}},"security":[]}},"/v1/me":{"get":{"operationId":"getMe","summary":"Who am I","tags":["Accounts"],"description":"You, the tenant you act for (tenant_id), its plan, your scope and a key's namespaces.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/keys":{"get":{"operationId":"listKeys","summary":"List keys","tags":["Keys"],"description":"The tenant's live keys. Needs an admin key without namespace limits or caps, or the owner's login.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Key"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"operationId":"createKey","summary":"Make an API key","tags":["Keys"],"description":"The key is in the answer only. Scopes: read: list namespaces, search, count, get documents, warm, SQL, usage. write: also create and delete namespaces, write, update, delete and import documents. admin: also make, list, change and revoke keys, and billing. Only an admin key without namespace limits or caps (or the owner's login) can manage keys, so a read or write key can't make more.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyRequest"},"examples":{"agent":{"summary":"A write key for an agent","value":{"name": "my agent"}},"limited":{"summary":"A read-only key for one namespace, capped","value":{"name": "help bot", "scope": "read", "namespaces": ["help-center"], "rate_per_s": 10, "cap_queries": 100000}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewKey"},"example":{"id": 7, "name": "my agent", "key": "tb_3q2-Xw9Lk2bFz0aQvP1mYc7RnT4sUe8d", "prefix": "tb_3q2-Xw9", "scope": "write", "namespaces": []}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/v1/keys/{id}":{"delete":{"operationId":"revokeKey","summary":"Revoke a key","tags":["Keys"],"description":"At once (an MCP connection's key too).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/keys/{id}/limits":{"put":{"operationId":"setKeyLimits","summary":"Change a key's namespaces and caps","tags":["Keys"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyLimits"},"examples":{"caps":{"summary":"Cap a key","value":{"namespaces": ["agent-7-*"], "rate_per_s": 5, "cap_queries": 50000, "cap_writes": 10000}}}}}},"responses":{"204":{"description":"Done (no body)"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/v1/embedding-keys":{"get":{"operationId":"listEmbeddingKeys","summary":"List embedding keys","tags":["Embeddings"],"description":"The tenant's own provider keys SkimmerDB embeds with (bring your own key): which providers, and a hint of each key. Needs an admin key without namespace limits or caps, or the owner's login (as managing API keys).","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbeddingKeyList"},"example":{"keys": [{"provider": "openai", "hint": "sk-…a1b2", "created_at": "2026-10-16T09:00:00Z", "updated_at": "2026-10-16T09:00:00Z"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/v1/embedding-keys/{provider}":{"put":{"operationId":"setEmbeddingKey","summary":"Store an embedding key","tags":["Embeddings"],"description":"Stores (or replaces) the tenant's key for a provider (openai, voyage, gemini or cohere), after checking it with the provider (one tiny embedding with its default model, billed by the provider). Kept encrypted; never shown again. Namespaces with an embedding model (embed) then embed documents' text and semantic searches with it; the provider bills the tenant, SkimmerDB counts the tokens (usage.embed_tokens) and charges nothing for them. Needs an admin key without namespace limits or caps, or the owner's login (as managing API keys).","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbeddingKeyRequest"},"examples":{"openai":{"summary":"An OpenAI key","value":{"key": "sk-proj-..."}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbeddingKey"},"example":{"provider": "openai", "hint": "sk-…a1b2", "created_at": "2026-10-16T09:00:00Z", "updated_at": "2026-10-16T09:00:00Z"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/BadGateway"}}},"delete":{"operationId":"deleteEmbeddingKey","summary":"Delete an embedding key","tags":["Embeddings"],"description":"Forgets the tenant's key for a provider at once. Writes that need it (text without vectors in a namespace with that provider's model) and semantic searches then answer 422 embedding_key_missing. Needs an admin key without namespace limits or caps, or the owner's login (as managing API keys).","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/tenants":{"get":{"operationId":"listTenants","summary":"List your tenants","tags":["Tenants"],"description":"With your role in each (a key sees its own tenant).","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Tenant"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"createTenant","summary":"Create a tenant","tags":["Tenants"],"description":"A new tenant you own (a login only: keys belong to one tenant). Act for it with X-Tenant: <id>.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantRequest"},"examples":{"tenant":{"summary":"A second tenant","value":{"name": "Acme staging"}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tenant"},"example":{"id": 43, "name": "Acme staging", "plan": "free", "status": "active", "role": "owner", "created_at": "2026-10-06T09:00:00Z"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/v1/tenants/{id}":{"delete":{"operationId":"deleteTenant","summary":"Delete a tenant","tags":["Tenants"],"description":"The tenant, its namespaces, keys and usage, for good (its owner; not your account's own tenant).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/v1/usage":{"get":{"operationId":"getUsage","summary":"Usage and bill this period","tags":["Usage"],"description":"This billing period so far and what it costs. Metered per tenant per UTC day: storage (GB-months), documents and bytes written, queries and the object-storage reads they made, the MCP part of queries and writes.","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"boolean"},"description":"Add the period day by day (days)."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Usage"},"example":{"period_start": "2026-10-01", "month": "2026-10", "queries": 1830, "writes": 52040, "write_bytes": 54100231, "docs_written": 52000,
  "store_gets": 412, "store_bytes": 3100220, "cpu_ms": 912.5, "out_bytes": 8120544, "mcp_queries": 640, "mcp_writes": 12, "embed_tokens": 0, "stored_bytes": 29411022, "gb_months": 0.0021,
  "plan": {"name": "free", "price_month": 0.0, "included_gb_months": 1.0, "included_write_gb": 1.0, "included_queries": 100000, "included_writes": 100000,
    "included_embed_tokens": 0, "per_gb_month": 0.0, "per_gb_written": 0.0, "per_million_queries": 0.0, "per_million_embed_tokens": 0.0,
    "per_million_store_gets": 0.0, "hard_limit": true, "qps": 100, "concurrency": 16, "storage_quota_gb": 1.0, "read_inflight_mb": 64,
    "sql_seconds": 10, "sql_concurrency": 2, "query_seconds": 10, "max_namespaces": 1000,
    "included_cpu_seconds": 3600.0, "included_store_gets": 1000000, "included_out_gb": 10.0, "max_creates_day": 1000, "unverified_share": 0.01},
  "bill": {"base": 0.0, "storage": 0.0, "writes": 0.0, "queries": 0.0, "embedding": 0.0, "reads": 0.0, "usage": 0.0, "total": 0.0}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/billing":{"get":{"operationId":"getBilling","summary":"Plan and bill","tags":["Billing"],"description":"The plan, whether billing is set up, and this period's usage and bill. An admin key or the owner's login.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/v1/billing/checkout":{"post":{"operationId":"createCheckout","summary":"Upgrade to pro (Stripe Checkout)","tags":["Billing"],"description":"A Stripe Checkout page subscribing the tenant to pro: open url in a browser.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedirectUrl"},"example":{"url": "https://checkout.stripe.com/c/pay/cs_live_a1b2c3"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"}}}},"/v1/billing/portal":{"post":{"operationId":"createPortal","summary":"Billing portal (Stripe)","tags":["Billing"],"description":"The Stripe Customer Portal: cards, invoices, cancelling.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedirectUrl"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"}}}},"/v1/namespaces":{"post":{"operationId":"createNamespace","summary":"Create a namespace","tags":["Namespaces"],"description":"An idle one costs only the bytes it stores (free plan: up to 1,000 namespaces; pro: no limit). Give it vector to store embeddings with a metric of your choice. Optional: writing documents to a name that doesn't exist makes the namespace.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateNamespace"},"examples":{"text":{"summary":"Text and metadata","value":{"name": "support"}},"vectors":{"summary":"With vectors (a toy 3-d example; real embeddings have hundreds of dims)","value":{"name": "notes", "vector": {"dims": 3, "metric": "cosine"}}},"embed":{"summary":"Embedded by SkimmerDB with your OpenAI key","value":{"name": "kb", "embed": {"provider": "openai", "model": "text-embedding-3-small", "dims": 512}}}}}}},"responses":{"201":{"description":"Created","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Namespace"},"example":{"name": "support", "docs_written": 0, "bytes_written": 0, "stored_bytes": 0, "created_at": "2026-10-06T09:00:00Z"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}}},"get":{"operationId":"listNamespaces","summary":"List namespaces","tags":["Namespaces"],"description":"By name, a page at a time.","parameters":[{"name":"after","in":"query","required":false,"schema":{"type":"string"},"description":"The next of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":100},"description":"Namespaces a page."}],"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NamespaceList"},"example":{"namespaces": [{"name": "support", "docs_written": 2, "bytes_written": 120, "stored_bytes": 2817, "created_at": "2026-10-06T09:00:00Z"}], "next": ""}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/namespaces/{name}":{"patch":{"operationId":"updateNamespace","summary":"Change a namespace's embedding model","tags":["Namespaces"],"description":"Sets, changes or removes the model documents' text is embedded with (the tenant's own provider key; see Embeddings). A namespace's vector length is fixed: the model must make vectors of it (409 embedding_dims otherwise, and for a namespace made without vectors). Documents already written keep their vectors.","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NamespacePatch"},"examples":{"set":{"summary":"Embed with OpenAI","value":{"embed": {"provider": "openai", "model": "text-embedding-3-small"}}},"stop":{"summary":"Stop embedding","value":{"embed": null}}}}}},"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Namespace"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/BadGateway"}}},"get":{"operationId":"getNamespace","summary":"Get a namespace","tags":["Namespaces"],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."}],"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Namespace"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"delete":{"operationId":"deleteNamespace","summary":"Delete a namespace","tags":["Namespaces"],"description":"Its documents are deleted from storage right after.","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."}],"responses":{"204":{"description":"Done (no body)","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/namespaces/{name}/docs":{"post":{"operationId":"writeDocuments","summary":"Write documents","tags":["Documents"],"description":"Put (insert or replace) and delete in one request. Answers once the write is stored in object storage (about 250 ms); searchable on this server at once, everywhere within a second; wait: true answers once it's searchable here. A namespace that doesn't exist is made by the write (created: true in the answer): with vectors of the first vector's length and cosine when the documents have vectors, else text only (create it first for euclidean); past the plan's namespace count, 402 namespace_limit. Metered as documents written and bytes written (see Usage).","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteRequest"},"examples":{"docs":{"summary":"Two documents with metadata","value":{"wait": true, "docs": [
  {"id": "t-1", "text": "Refund requested for order ACME-1042", "meta": {"status": "open", "priority": 3}},
  {"id": "t-2", "text": "Login fails with error E401 on mobile", "meta": {"status": "closed", "priority": 1}}]}},"vectors":{"summary":"Documents with vectors (namespace made with dims 3)","value":{"wait": true, "docs": [{"id": "n-1", "text": "Webhook timeouts since Tuesday", "meta": {"team": "api"}, "vector": [0.12, -0.03, 0.99]}, {"id": "n-2", "text": "Dark mode request", "meta": {"team": "web"}, "vector": [0.9, 0.1, 0.05]}]}},"put_and_delete":{"summary":"Put one, delete one","value":{"docs": [{"id": "t-3", "text": "Webhook timeouts since Tuesday", "meta": {"team": "api"}}], "deletes": ["t-2"]}}}}}},"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteResult"},"example":{"written": 2, "deleted": 0, "version": 3, "stored_bytes": 2817, "created": false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/Unavailable"},"507":{"$ref":"#/components/responses/InsufficientStorage"}},"x-metered":"write"},"patch":{"operationId":"updateDocuments","summary":"Update documents","tags":["Documents"],"description":"Text replaced if given, meta merged key by key (null removes a key), the vector kept unless a new one is given. The documents must exist. Metered as documents written and bytes written (see Usage).","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRequest"},"examples":{"close":{"summary":"Close a ticket","value":{"docs": [{"id": "t-1", "meta": {"status": "closed", "priority": null}}], "wait": true}}}}}},"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/Unavailable"},"507":{"$ref":"#/components/responses/InsufficientStorage"}},"x-metered":"write"}},"/v1/namespaces/{name}/delete":{"post":{"operationId":"deleteDocuments","summary":"Delete documents","tags":["Documents"],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteRequest"},"examples":{"ids":{"summary":"By id","value":{"ids": ["t-2"], "wait": true}}}}}},"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteResult"},"example":{"written": 0, "deleted": 1, "version": 4, "stored_bytes": 2817, "created": false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}},"x-metered":"write"}},"/v1/namespaces/{name}/docs/{id}":{"get":{"operationId":"getDocument","summary":"Get a document","tags":["Documents"],"description":"One whole, with its vector. Ids may hold slashes (the rest of the path is the id). Counts as one query (metered; see Usage).","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The rest of the path (may hold slashes)."}],"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoredDocument"},"example":{"id": "t-1", "text": "Refund requested for order ACME-1042", "meta": {"priority": 3, "status": "open"}, "written_at": "2026-10-05T01:30:12Z", "temperature": "warm", "store_gets": 0, "store_bytes": 0, "cpu_ms": 0.03, "timed_out": false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}},"x-metered":"query"}},"/v1/namespaces/{name}/import":{"post":{"operationId":"importRows","summary":"Bulk import a file","tags":["Documents"],"description":"CSV, NDJSON or Parquet as the request body (up to 2 GB and 1M rows), parsed straight into one segment: all rows become visible together, or none. A namespace that doesn't exist is made by the import (text only; created: true). Metered as a write of that many documents and the file's bytes.","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["csv","ndjson","parquet"],"default":"csv"},"description":"The file's format."},{"name":"id","in":"query","required":false,"schema":{"type":"string"},"description":"The column holding document ids (default: row numbers)."},{"name":"text","in":"query","required":false,"schema":{"type":"string"},"description":"Columns to index as text, comma-separated."},{"name":"schema","in":"query","required":false,"schema":{"type":"string"},"description":"CSV: a CREATE TABLE giving the columns' types (default: from the header, kinds inferred)."}],"requestBody":{"required":true,"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportResult"},"example":{"written": 2, "deleted": 0, "version": 5, "stored_bytes": 4096, "rows": 2, "took_ms": 12, "created": false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"},"507":{"$ref":"#/components/responses/InsufficientStorage"}},"x-metered":"write"}},"/v1/namespaces/{name}/search":{"post":{"operationId":"search","summary":"Search","tags":["Search"],"description":"Exact text (literal, ignoring case) or regex (RE2), filtered by metadata, newest first with the matched text and its line; or, with vector, the nearest documents (alone, or hybrid with q and filter). Filters prune before any document is read. Counts as one query (metered; see Usage).","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"},"examples":{"text":{"summary":"Plain text, filtered","value":{"q": "acme-1042", "filter": {"status": "open"}}},"regex":{"summary":"Regex and a numeric range","value":{"q": "E[0-9]{3}", "regex": true, "filter": {"priority": {"gte": 1, "lt": 4}}, "limit": 50}},"list":{"summary":"List documents (empty q), one of several values","value":{"q": "", "filter": {"status": ["open", "pending"]}, "limit": 20}},"vector":{"summary":"Nearest documents to a vector","value":{"vector": [0.1, 0.0, 0.98], "limit": 10}},"hybrid":{"summary":"Nearest among those matching text and a filter","value":{"vector": [0.1, 0.0, 0.98], "q": "timeout", "filter": {"team": "api"}, "limit": 10}},"semantic":{"summary":"By meaning, with the namespace's embedding model","value":{"semantic": "how do I get my money back", "filter": {"status": "open"}, "limit": 10}}}}}},"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResult"},"example":{"namespace": "support", "hits": [{"id": "t-1", "match": "ACME-1042", "line": "Refund requested for order ACME-1042", "meta": {"priority": 3, "status": "open"}, "written_at": "2026-10-05T01:30:12Z"}], "next": "", "docs": 2, "took_ms": 0.41, "partial": false, "temperature": "warm", "store_gets": 0, "store_bytes": 0, "cpu_ms": 0.12, "timed_out": false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/Unavailable"}},"x-metered":"query"},"get":{"operationId":"searchGet","summary":"Search (query string)","tags":["Search"],"description":"Text search as GET, the same answer as POST. Counts as one query (metered; see Usage).","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."},{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Text (or a regex with regex=1); empty lists documents."},{"name":"regex","in":"query","required":false,"schema":{"type":"boolean"},"description":"q is an RE2 pattern."},{"name":"filter","in":"query","required":false,"schema":{"type":"string"},"description":"A filter as JSON text, e.g. {\"status\":\"open\"}."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000},"description":"Hits a page (20)."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"The next of the previous page."},{"name":"scan","in":"query","required":false,"schema":{"type":"boolean"},"description":"Allow a regex the index can't narrow to scan a big namespace."}],"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}},"x-metered":"query"}},"/v1/namespaces/{name}/count":{"post":{"operationId":"count","summary":"Count","tags":["Search"],"description":"Exact: every visible document that matches. exact: false estimates from a sample (lo..hi a 95% interval). Counts as one query (metered; see Usage).","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CountRequest"},"examples":{"exact":{"summary":"Exact count","value":{"q": "refund", "filter": {"status": "open"}}},"estimate":{"summary":"An estimate on a big namespace","value":{"q": "refund", "exact": false, "sample": 1000}}}}}},"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CountResult"},"example":{"namespace": "support", "count": 1, "exact": true, "sample": ["t-1"], "docs": 2, "took_ms": 0.2, "partial": false, "temperature": "warm", "store_gets": 0, "store_bytes": 0, "lo": 1, "hi": 1, "candidates": 1, "cpu_ms": 0.05}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}},"x-metered":"query"},"get":{"operationId":"countGet","summary":"Count (query string)","tags":["Search"],"description":"Count as GET. Counts as one query (metered; see Usage).","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."},{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Text (or a regex with regex=1); empty lists documents."},{"name":"regex","in":"query","required":false,"schema":{"type":"boolean"},"description":"q is an RE2 pattern."},{"name":"filter","in":"query","required":false,"schema":{"type":"string"},"description":"A filter as JSON text, e.g. {\"status\":\"open\"}."},{"name":"scan","in":"query","required":false,"schema":{"type":"boolean"},"description":"Allow a regex the index can't narrow to scan a big namespace."},{"name":"exact","in":"query","required":false,"schema":{"type":"boolean"},"description":"0: estimate."},{"name":"sample","in":"query","required":false,"schema":{"type":"integer","minimum":0},"description":"Estimates: candidates checked."}],"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CountResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}},"x-metered":"query"}},"/v1/namespaces/{name}/warm":{"post":{"operationId":"warmNamespace","summary":"Warm a namespace","tags":["Search"],"description":"Opens the namespace and fetches what these searches will need, so an agent about to work on it gets warm answers from its first query. Counts as one query (metered; see Usage).","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WarmRequest"},"examples":{"warm":{"summary":"Before an agent's session","value":{"queries": ["refund", "ERR-404"]}}}}}},"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WarmResult"},"example":{"namespace": "support", "temperature": "cold", "store_gets": 3, "store_bytes": 18211, "took_ms": 41.5}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}},"x-metered":"query"}},"/v1/namespaces/{name}/sql":{"post":{"operationId":"querySql","summary":"SQL over a namespace (beta)","tags":["SQL (beta)"],"description":"Beta: the dialect may grow. One SELECT ... FROM docs over the documents' metadata (columns: id and every top-level meta key, typed from the values: int (booleans 1/0), float or text; missing is NULL; SHOW COLUMNS lists them), with contains(text, '...') and grep(text, 'regex') through the text index. WHERE, GROUP BY, HAVING, ORDER BY, LIMIT/OFFSET, COUNT, COUNT(DISTINCT), SUM, AVG, MIN, MAX, CASE WHEN, COALESCE, LIKE, IN, IS NULL, DATE_TRUNC('minute'|'hour'|'day', x) (x an integer of seconds or a time column), EXTRACT(MINUTE FROM x), REGEXP_REPLACE, LENGTH (bytes); arithmetic + and -. Not yet: joins, subqueries, UNION, window functions, * / %. Answers stop at 10,000 rows (truncated). Guide and patterns: https://skimmerdb.com/docs#analytics. A time limit per plan (10 s free, 30 s pro) and SQL queries in flight per tenant (2 free, 8 pro; 429 past it). Errors in the SQL are 422 with the reason; 413 when it would read too much. Counts as one query (metered; see Usage).","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SqlRequest"},"examples":{"group":{"summary":"Group and aggregate, with a text match","value":{"sql": "SELECT status, COUNT(*) AS n, AVG(priority) AS p FROM docs WHERE contains(text, 'refund') GROUP BY status ORDER BY n DESC"}},"series":{"summary":"An hourly series over integer-second timestamps","value":{"sql": "SELECT DATE_TRUNC('hour', ts) AS hour, COUNT(*) AS calls, SUM(CASE WHEN ok = 0 THEN 1 ELSE 0 END) AS errors FROM docs WHERE ts >= 1760572800 GROUP BY hour ORDER BY hour"}},"grep":{"summary":"Group what a regex found","value":{"sql": "SELECT agent, COUNT(*) AS n FROM docs WHERE grep(text, 'E5[0-9]{2}') GROUP BY agent ORDER BY n DESC"}},"columns":{"summary":"The columns and their types","value":{"sql": "SHOW COLUMNS"}}}}}},"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SqlResult"},"example":{"namespace": "support", "columns": ["status", "n", "p"], "types": ["text", "int", "float"], "rows": [["open", 1, 3.0]], "chunks": 1, "scanned": 1, "partial": false, "truncated": false, "took_ms": 0.9, "temperature": "warm", "store_gets": 0, "store_bytes": 0, "cpu_ms": 0.4, "timed_out": false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}},"x-metered":"query","x-beta":true}},"/v1/namespaces/{name}/sidecars":{"get":{"operationId":"listSidecars","summary":"List a linked namespace's sidecar files","tags":["Linked namespaces"],"description":"Per-segment files carried with an index linked by reference (an operator links one). Other namespaces have none.","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."}],"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SidecarList"},"example":{"namespace": "web", "sidecars": [{"seg": "seg-000123", "name": "tags", "size": 81234}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/namespaces/{name}/sidecars/{seg}/{file}":{"get":{"operationId":"getSidecar","summary":"Read a sidecar file","tags":["Linked namespaces"],"description":"Its bytes (at most 64 MB a call). Counts as one query (metered; see Usage).","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The namespace's name."},{"name":"seg","in":"path","required":true,"schema":{"type":"string"}},{"name":"file","in":"path","required":true,"schema":{"type":"string"}},{"name":"off","in":"query","required":false,"schema":{"type":"integer","minimum":0},"description":"Offset."},{"name":"n","in":"query","required":false,"schema":{"type":"integer","minimum":0},"description":"Bytes (0: to the end)."}],"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"x-metered":"query"}},"/mcp":{"post":{"operationId":"mcp","summary":"MCP endpoint (Streamable HTTP)","tags":["MCP"],"description":"For Claude, ChatGPT, Codex, Cursor and other agents: JSON-RPC 2.0 (initialize, tools/list, tools/call). Tools: list_namespaces, create_namespace, delete_namespace, write_documents, delete_documents, search, sql_query, count, get_document, warm_namespace; each runs the same code, limits and metering as its REST call (metered apart as mcp_queries / mcp_writes). Agents sign in with OAuth 2.1 (discovery from the 401's WWW-Authenticate, dynamic registration, PKCE); API keys work too. Connect: claude mcp add --transport http skimmerdb https://api.skimmerdb.com/mcp (https://skimmerdb.com/mcp is the same endpoint).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcRequest"},"examples":{"tools_list":{"summary":"The tools this key may call","value":{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}},"search":{"summary":"Call the search tool","value":{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "search", "arguments": {"namespace": "support", "query": "refund", "filter": {"status": "open"}}}}}}}}},"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"},"example":{"jsonrpc": "2.0", "id": 1, "result": {"tools": [{"name": "search"}]}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/.well-known/oauth-protected-resource":{"get":{"operationId":"getResourceMetadata","summary":"OAuth protected-resource metadata","tags":["MCP"],"description":"Where MCP clients find the OAuth server (RFC 9728).","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceMetadata"}}}}},"security":[]}}},"components":{"schemas":{"Bill":{"type":"object","properties":{"base":{"type":"number","format":"double"},"storage":{"type":"number","format":"double"},"writes":{"type":"number","format":"double"},"queries":{"type":"number","format":"double"},"embedding":{"type":"number","format":"double"},"reads":{"type":"number","format":"double"},"usage":{"type":"number","format":"double"},"total":{"type":"number","format":"double"}},"required":["base","storage","writes","queries","embedding","reads","usage","total"]},"Billing":{"type":"object","properties":{"plan":{"$ref":"#/components/schemas/Plan"},"stripe":{"type":"boolean"},"customer":{"type":"boolean"},"subscribed":{"type":"boolean"},"usage":{"$ref":"#/components/schemas/Usage"}},"required":["plan","stripe","customer","subscribed","usage"]},"CountRequest":{"type":"object","properties":{"q":{"type":"string","default":""},"regex":{"type":"boolean","default":false},"filter":{"$ref":"#/components/schemas/Filter"},"exact":{"type":"boolean","default":true,"description":"false: estimate from a sample of the candidates (fast on big namespaces; no filters)."},"sample":{"type":"integer","minimum":0,"description":"Estimates: candidates checked (2,000)."},"scan":{"type":"boolean","default":false,"description":"Allow a regex the index can't narrow to scan a big namespace (otherwise 422 scan_refused)."}}},"CountResult":{"type":"object","properties":{"namespace":{"type":"string"},"count":{"type":"integer","format":"int64"},"exact":{"description":"false: count is an estimate (lo..hi is its 95% interval).","type":"boolean"},"sample":{"description":"Some matching ids.","type":"array","items":{"type":"string"}},"docs":{"type":"integer","format":"int64"},"took_ms":{"type":"number","format":"double"},"partial":{"type":"boolean"},"temperature":{"description":"How warm the namespace was: warm (open), local (opened from the server's disk) or cold (opened from object storage).","type":"string"},"store_gets":{"type":"integer","format":"int64"},"store_bytes":{"type":"integer","format":"int64"},"lo":{"type":"integer","format":"int64"},"hi":{"type":"integer","format":"int64"},"candidates":{"type":"integer","format":"int64"},"cpu_ms":{"type":"number","format":"double"}},"required":["namespace","count","exact","sample","docs","took_ms","partial","temperature","store_gets","store_bytes","lo","hi","candidates","cpu_ms"]},"CreateNamespace":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-z0-9._-]{1,64}$","description":"1 to 64 of a-z, 0-9, '-', '_' and '.'."},"vector":{"$ref":"#/components/schemas/VectorConfig","description":"Give the namespace vectors (fixed for its life). Leave out for text only."},"embed":{"$ref":"#/components/schemas/EmbedConfig","description":"Embed documents' text with a model and the tenant's own key: the namespace gets vectors of the model's length."}},"required":["name"]},"DeleteRequest":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"}},"wait":{"type":"boolean","default":false}},"required":["ids"]},"Document":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":512,"description":"Any string up to 512 bytes, one line (slashes allowed). Writing an id again replaces the document, so use a stable natural key (ticket-1042, docs/install.md#3, agent-1/run-42/step-3): retries are then safe."},"text":{"type":"string","description":"What q searches (exact text and regex): up to 2 MB. May be left out when the document has a vector."},"meta":{"type":"object","additionalProperties":true,"description":"Metadata: a JSON object of strings, numbers and booleans you filter, group and sort by (up to 16 KB); its top-level keys are SQL columns. Keep it flat (nested objects and arrays can't be filtered), one type per key, times as integer seconds (ranges and DATE_TRUNC need numbers). Not searched by q: put searchable words in text."},"vector":{"type":"array","items":{"type":"number"},"description":"An embedding: as many numbers as the namespace's dims."},"vector_b64":{"type":"string","contentEncoding":"base64","description":"The vector as base64 of little-endian float32s (smaller than JSON numbers)."}},"required":["id"]},"DocumentPatch":{"type":"object","properties":{"id":{"type":"string","minLength":1},"text":{"type":"string","description":"Replaces the text (kept when left out)."},"meta":{"type":"object","additionalProperties":true,"description":"Merged key by key; a null value removes the key."},"vector":{"type":"array","items":{"type":"number"},"description":"An embedding: as many numbers as the namespace's dims."}},"required":["id"]},"EmbedConfig":{"type":"object","properties":{"provider":{"type":"string","enum":["openai","voyage","gemini","cohere","skimmerdb"],"description":"openai, voyage, gemini or cohere: with the tenant's own key for it (PUT /v1/embedding-keys/{provider}; the provider bills the tenant, SkimmerDB charges nothing). skimmerdb: hosted, SkimmerDB embeds with voyage-3.5-lite at Voyage's price, no margin (metered as embed_tokens; the free plan includes some)."},"model":{"type":"string","description":"The provider's embedding model (default: text-embedding-3-small for openai, voyage-3.5-lite for voyage and skimmerdb, gemini-embedding-001 for gemini, embed-v4.0 for cohere). skimmerdb takes voyage-3.5-lite only."},"dims":{"type":"integer","minimum":1,"maximum":2048,"description":"At creation: the vectors' length to ask the model for (default: the model's own, up to 2,048). Models that make several lengths (OpenAI's text-embedding-3, Voyage's 3.5, Gemini's, Cohere's embed-v4.0) take it."}},"required":["provider"],"description":"Documents written with text and no vector are embedded with this model (with the tenant's own provider key, or hosted); search takes semantic."},"EmbeddingKey":{"type":"object","properties":{"provider":{"description":"openai or voyage.","type":"string"},"hint":{"description":"The key's first and last characters: the key itself is never shown again.","type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["provider","hint","created_at","updated_at"]},"EmbeddingKeyList":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/EmbeddingKey"}}},"required":["keys"]},"EmbeddingKeyRequest":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":500,"description":"The provider's API key. Checked with the provider (one tiny embedding), then stored encrypted; never shown again."}},"required":["key"]},"Error":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."},"code":{"type":"string","description":"A word to act on. By status: bad_request (400), unauthorized (401), payment_required (402), forbidden (403), not_found (404), conflict (409), too_large (413), invalid (422; fields says which), rate_limited (429), internal (500), not_implemented (501), bad_gateway (502), unavailable (503), quota_full (507). More precise ones: namespace_limit (402: the plan's namespace count, or its namespaces made today), email_unverified (402: a free tenant whose owner hasn't verified their email has 1% of the plan; verify it), query_timeout (422: past the plan's time limit), scan_refused (422: a regex the index can't narrow on a big namespace; send scan: true), write_busy (429: writes arrive faster than the namespace commits them; nothing was written), write_unknown (503: the write may or may not have been applied; retrying is safe), embedding_key_missing (422: the namespace embeds with a provider the tenant has no key for), payment_required (402: also the free plan's hosted embedding tokens), embedding_key_invalid (422: the provider refused the tenant's key), embedding_rejected (422: the provider refused the request, e.g. an unknown model), embedding_dims (409: the model can't make vectors of the namespace's length), embedding_rate_limited (429: the provider is rate-limiting the tenant's key; retry_after), embedding_failed (502: the provider failed; nothing was written, retry)."},"fields":{"type":"object","additionalProperties":{"type":"string"},"description":"invalid: field -> what's wrong with it."},"retry_after":{"type":"integer","description":"write_busy: seconds to wait (as Retry-After)."}},"required":["error","code"],"description":"Every error answer, whatever the status."},"Filter":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]}},{"$ref":"#/components/schemas/FilterOps"}]},"description":"Every key must hold. {\"status\": \"open\"} equal; {\"status\": [\"open\", \"pending\"]} or {\"status\": {\"in\": [...]}} one of; {\"priority\": {\"gte\": 2, \"lt\": 5}} a numeric range (gt, gte, lt, lte); {\"team\": {\"ne\": \"api\"}} not equal (a missing key counts as not equal). Numbers compare as numbers."},"FilterOps":{"type":"object","properties":{"eq":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]},"ne":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]},"in":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]}},"gt":{"type":"number"},"gte":{"type":"number"},"lt":{"type":"number"},"lte":{"type":"number"}},"additionalProperties":false},"Hit":{"type":"object","properties":{"id":{"type":"string"},"match":{"type":"string","description":"Text search: the matched text."},"line":{"type":"string","description":"Text search: the line around the match."},"score":{"type":"number","description":"Vector search: cosine similarity (cosine namespaces) or euclidean distance."},"meta":{"type":"object","additionalProperties":true},"written_at":{"type":"string","format":"date-time"}},"required":["id","meta","written_at"]},"ImportResult":{"type":"object","properties":{"written":{"type":"integer","format":"int64"},"deleted":{"type":"integer","format":"int64"},"version":{"type":"integer","format":"int64"},"stored_bytes":{"type":"integer","format":"int64"},"rows":{"type":"integer","format":"int64"},"took_ms":{"type":"number"},"created":{"type":"boolean","description":"The namespace didn't exist: this import made it (text only)."}},"required":["written","deleted","version","stored_bytes","rows","took_ms","created"]},"JsonRpcRequest":{"type":"object","properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"type":["integer","string"]},"method":{"type":"string","description":"initialize, ping, tools/list or tools/call."},"params":{"type":"object","additionalProperties":true}},"required":["jsonrpc","method"]},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"type":["integer","string","null"]},"result":{"type":"object","additionalProperties":true},"error":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}},"additionalProperties":true}},"required":["jsonrpc","id"]},"Key":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"prefix":{"type":"string"},"connected_app":{"description":"Made by an OAuth approval (an MCP client).","type":"boolean"},"scope":{"type":"string"},"namespaces":{"type":"array","items":{"type":"string"}},"rate_per_s":{"type":"integer","format":"int64"},"cap_queries":{"type":"integer","format":"int64"},"cap_writes":{"type":"integer","format":"int64"},"created_at":{"type":"string"},"last_used_at":{"type":"string"}},"required":["id","name","prefix","connected_app","scope","namespaces","rate_per_s","cap_queries","cap_writes","created_at","last_used_at"]},"KeyLimits":{"type":"object","properties":{"namespaces":{"type":"array","items":{"type":"string"}},"rate_per_s":{"type":"integer","minimum":0},"cap_queries":{"type":"integer","minimum":0},"cap_writes":{"type":"integer","minimum":0}}},"KeyRequest":{"type":"object","properties":{"name":{"type":"string","description":"A label (default: default)."},"scope":{"type":"string","enum":["read","write","admin"],"default":"write","description":"read: list namespaces, search, count, get documents, warm, SQL, usage. write: also create and delete namespaces, write, update, delete and import documents. admin: also make, list, change and revoke keys, and billing. Only an admin key without namespace limits or caps (or the owner's login) can manage keys, so a read or write key can't make more."},"namespaces":{"type":"array","items":{"type":"string"},"description":"The namespaces the key may use: names or prefix* (default: all)."},"rate_per_s":{"type":"integer","minimum":0,"description":"Requests a second (0: no cap of its own)."},"cap_queries":{"type":"integer","minimum":0,"description":"Queries a billing period (0: none)."},"cap_writes":{"type":"integer","minimum":0,"description":"Documents written or deleted a billing period (0: none)."}}},"LoginRequest":{"type":"object","properties":{"email":{"type":"string"},"password":{"type":"string"}},"required":["email","password"]},"Me":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"email":{"type":"string"},"name":{"type":"string"},"org_id":{"type":"integer","format":"int64"},"org_name":{"type":"string"},"plan":{"$ref":"#/components/schemas/Plan"},"tenant_id":{"description":"The tenant this call acts for.","type":"integer","format":"int64"},"scope":{"description":"read, write or admin.","type":"string"},"key_id":{"description":"The key used (0: a login).","type":"integer","format":"int64"},"namespaces":{"type":"array","items":{"type":"string"}},"is_admin":{"type":"boolean"},"email_verified":{"description":"false: until verified, a free tenant has 1% of the plan (plan shows what applies now).","type":"boolean"}},"required":["id","email","name","org_id","org_name","plan","tenant_id","scope","key_id","namespaces","is_admin","email_verified"]},"Namespace":{"type":"object","properties":{"embed":{"$ref":"#/components/schemas/EmbedConfig"},"vector":{"$ref":"#/components/schemas/VectorConfig"},"name":{"type":"string"},"docs_written":{"description":"Documents written to it so far (puts and deletes).","type":"integer","format":"int64"},"bytes_written":{"type":"integer","format":"int64"},"stored_bytes":{"description":"What its objects take in object storage now (metered as storage).","type":"integer","format":"int64"},"created_at":{"description":"RFC 3339, UTC.","type":"string"}},"required":["name","docs_written","bytes_written","stored_bytes","created_at"]},"NamespaceList":{"type":"object","properties":{"namespaces":{"type":"array","items":{"$ref":"#/components/schemas/Namespace"}},"next":{"description":"Pass as after for the next page (empty: no more).","type":"string"}},"required":["namespaces","next"]},"NamespacePatch":{"type":"object","properties":{"embed":{"anyOf":[{"$ref":"#/components/schemas/EmbedConfig"},{"type":"null"}],"description":"Set or change the embedding model (it must make vectors of the namespace's length), or null to stop embedding."}},"required":["embed"]},"NewKey":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"key":{"description":"The key itself: shown only here. Send it as Authorization: Bearer <key>.","type":"string"},"prefix":{"type":"string"},"scope":{"type":"string"},"namespaces":{"type":"array","items":{"type":"string"}}},"required":["id","name","key","prefix","scope","namespaces"]},"Plan":{"type":"object","properties":{"name":{"type":"string"},"price_month":{"type":"number","format":"double"},"included_gb_months":{"type":"number","format":"double"},"included_write_gb":{"type":"number","format":"double"},"included_queries":{"type":"integer","format":"int64"},"included_writes":{"type":"integer","format":"int64"},"included_embed_tokens":{"type":"integer","format":"int64"},"per_gb_month":{"type":"number","format":"double"},"per_gb_written":{"type":"number","format":"double"},"per_million_queries":{"type":"number","format":"double"},"per_million_embed_tokens":{"type":"number","format":"double"},"per_million_store_gets":{"type":"number","format":"double"},"hard_limit":{"type":"boolean"},"qps":{"type":"integer","format":"int64"},"concurrency":{"type":"integer","format":"int64"},"storage_quota_gb":{"type":"number","format":"double"},"read_inflight_mb":{"type":"integer","format":"int64"},"sql_seconds":{"type":"integer","format":"int64"},"sql_concurrency":{"type":"integer","format":"int64"},"query_seconds":{"type":"integer","format":"int64"},"max_namespaces":{"type":"integer","format":"int64"},"included_cpu_seconds":{"type":"number","format":"double"},"included_store_gets":{"type":"integer","format":"int64"},"included_out_gb":{"type":"number","format":"double"},"max_creates_day":{"type":"integer","format":"int64"},"unverified_share":{"type":"number","format":"double"}},"required":["name","price_month","included_gb_months","included_write_gb","included_queries","included_writes","included_embed_tokens","per_gb_month","per_gb_written","per_million_queries","per_million_embed_tokens","per_million_store_gets","hard_limit","qps","concurrency","storage_quota_gb","read_inflight_mb","sql_seconds","sql_concurrency","query_seconds","max_namespaces","included_cpu_seconds","included_store_gets","included_out_gb","max_creates_day","unverified_share"]},"RedirectUrl":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]},"ResendResult":{"type":"object","properties":{"verified":{"type":"boolean"},"sent":{"type":"boolean"}},"required":["verified","sent"]},"ResourceMetadata":{"type":"object","properties":{"resource":{"type":"string"},"authorization_servers":{"type":"array","items":{"type":"string"}},"scopes_supported":{"type":"array","items":{"type":"string"}},"bearer_methods_supported":{"type":"array","items":{"type":"string"}},"resource_name":{"type":"string"},"resource_documentation":{"type":"string"}},"required":["resource","authorization_servers","scopes_supported","bearer_methods_supported","resource_name","resource_documentation"]},"SearchRequest":{"type":"object","properties":{"q":{"type":"string","default":"","description":"Plain text, matched literally and ignoring case; with regex an RE2 pattern. Empty: list documents, newest first. With a vector: only documents containing it (hybrid)."},"regex":{"type":"boolean","default":false},"filter":{"$ref":"#/components/schemas/Filter"},"limit":{"type":"integer","minimum":1,"maximum":1000,"description":"Hits a page (20; vector search 10)."},"semantic":{"type":"string","description":"Search by meaning with a text, in a namespace with an embedding model (embed): it is embedded with that model and the tenant's key, then searched like vector (q and filter narrow it)."},"cursor":{"type":"string","description":"The next of the previous page."},"scan":{"type":"boolean","default":false,"description":"Allow a regex the index can't narrow to scan a big namespace (otherwise 422 scan_refused)."},"vector":{"type":"array","items":{"type":"number"},"description":"An embedding: as many numbers as the namespace's dims."},"vector_b64":{"type":"string","contentEncoding":"base64","description":"The vector as base64 of little-endian float32s (smaller than JSON numbers)."},"nprobe":{"type":"integer","minimum":0,"description":"Vector search: clusters probed in the biggest index (48)."},"rerank":{"type":"integer","minimum":0,"description":"Vector search: candidates reranked (64)."},"full":{"type":"boolean","default":false,"description":"Vector search: rerank with the float32 vectors as written."}},"description":"Text search, or with vector (or vector_b64) a vector search: the nearest documents among those passing filter and q."},"SearchResult":{"type":"object","properties":{"namespace":{"type":"string"},"hits":{"type":"array","items":{"$ref":"#/components/schemas/Hit"},"description":"Newest first (text) or nearest first (vector)."},"next":{"type":"string","description":"Text search: pass as cursor for the next page (empty: no more)."},"docs":{"type":"integer","format":"int64","description":"Documents in the namespace."},"took_ms":{"type":"number"},"partial":{"type":"boolean","description":"Object storage failed for some reads: hits may be missing."},"scanned":{"type":"integer","format":"int64","description":"Vector search: candidates estimated."},"reranked":{"type":"integer","format":"int64","description":"Vector search: candidates reranked."},"temperature":{"type":"string","description":"How warm the namespace was: warm (open), local (opened from the server's disk) or cold (opened from object storage)."},"store_gets":{"type":"integer","format":"int64","description":"Object-storage reads this query made (0 when warm)."},"store_bytes":{"type":"integer","format":"int64","description":"Bytes those reads fetched."},"cpu_ms":{"type":"number","description":"CPU the query used (metered, not billed)."},"timed_out":{"type":"boolean","description":"The query ran past the plan's time limit (query_seconds): the answer is partial."}},"required":["namespace","hits","docs","took_ms","partial","temperature","store_gets","store_bytes","cpu_ms","timed_out"]},"Sidecar":{"type":"object","properties":{"seg":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer","format":"int64"}},"required":["seg","name","size"]},"SidecarList":{"type":"object","properties":{"namespace":{"type":"string"},"sidecars":{"type":"array","items":{"$ref":"#/components/schemas/Sidecar"}}},"required":["namespace","sidecars"]},"Signup":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Your user id."},"tenant_id":{"type":"integer","format":"int64","description":"Your first tenant (it owns namespaces, keys and usage)."},"key":{"type":"string","description":"Your first API key, full access (scope admin): shown only here. Send it as Authorization: Bearer <key>. Make narrower keys for apps and agents with POST /v1/keys."},"key_id":{"type":"integer","format":"int64"},"key_prefix":{"type":"string","description":"The key's first characters, as key listings show it."},"scope":{"type":"string","description":"admin."}},"required":["id","tenant_id","key","key_id","key_prefix","scope"]},"SignupRequest":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":200},"name":{"type":"string","minLength":1,"maxLength":100,"description":"Your name; also your first tenant's name."},"password":{"type":"string","minLength":8,"maxLength":200}},"required":["email","name","password"]},"SqlRequest":{"type":"object","properties":{"sql":{"type":"string","description":"One SELECT ... FROM docs (or SHOW COLUMNS)."}},"required":["sql"]},"SqlResult":{"type":"object","properties":{"namespace":{"type":"string"},"columns":{"type":"array","items":{"type":"string"}},"types":{"type":"array","items":{"type":"string"},"description":"int, float or text, one a column."},"rows":{"type":"array","items":{"type":"array","items":{"type":["string","number","null"]}}},"chunks":{"type":"integer","format":"int64","description":"Column blocks in the namespace."},"scanned":{"type":"integer","format":"int64","description":"Column blocks read."},"partial":{"type":"boolean","description":"Reserved for response compatibility; incomplete SQL text reads fail with 503."},"truncated":{"type":"boolean","description":"More than 10,000 rows: these are the first."},"took_ms":{"type":"number"},"temperature":{"type":"string","description":"How warm the namespace was: warm (open), local (opened from the server's disk) or cold (opened from object storage)."},"store_gets":{"type":"integer","format":"int64","description":"Object-storage reads this query made (0 when warm)."},"store_bytes":{"type":"integer","format":"int64","description":"Bytes those reads fetched."},"cpu_ms":{"type":"number","description":"CPU the query used (metered, not billed)."},"timed_out":{"type":"boolean","description":"The query ran past the plan's time limit (query_seconds): the answer is partial."}},"required":["namespace","columns","types","rows","chunks","scanned","partial","truncated","took_ms","temperature","store_gets","store_bytes","cpu_ms","timed_out"]},"StoredDocument":{"type":"object","properties":{"id":{"type":"string"},"text":{"type":"string"},"meta":{"type":"object","additionalProperties":true},"vector":{"type":"array","items":{"type":"number"}},"written_at":{"type":"string","format":"date-time"},"temperature":{"type":"string","description":"How warm the namespace was: warm (open), local (opened from the server's disk) or cold (opened from object storage)."},"store_gets":{"type":"integer","format":"int64","description":"Object-storage reads this query made (0 when warm)."},"store_bytes":{"type":"integer","format":"int64","description":"Bytes those reads fetched."},"cpu_ms":{"type":"number","description":"CPU the query used (metered, not billed)."},"timed_out":{"type":"boolean","description":"The query ran past the plan's time limit (query_seconds): the answer is partial."}},"required":["id","text","meta","written_at","temperature","store_gets","store_bytes","cpu_ms","timed_out"]},"Tenant":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"plan":{"type":"string"},"status":{"type":"string"},"role":{"type":"string"},"created_at":{"type":"string"}},"required":["id","name","plan","status","role","created_at"]},"TenantRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100}},"required":["name"]},"Token":{"type":"object","properties":{"token":{"type":"string"}},"required":["token"]},"UpdateRequest":{"type":"object","properties":{"docs":{"type":"array","items":{"$ref":"#/components/schemas/DocumentPatch"}},"wait":{"type":"boolean","default":false}},"required":["docs"]},"Usage":{"type":"object","properties":{"days":{"type":"array","items":{"$ref":"#/components/schemas/UsageDay"}},"period_start":{"description":"This billing period's first day (UTC).","type":"string"},"month":{"type":"string"},"queries":{"description":"Searches, counts, reads, warms and SQL queries (metered).","type":"integer","format":"int64"},"writes":{"description":"Documents written or deleted.","type":"integer","format":"int64"},"write_bytes":{"type":"integer","format":"int64"},"docs_written":{"type":"integer","format":"int64"},"store_gets":{"description":"Object-storage reads made by queries (cold queries cost more).","type":"integer","format":"int64"},"store_bytes":{"type":"integer","format":"int64"},"mcp_queries":{"type":"integer","format":"int64"},"mcp_writes":{"type":"integer","format":"int64"},"embed_tokens":{"type":"integer","format":"int64"},"cpu_ms":{"description":"CPU queries used (metered; the free plan includes plan.included_cpu_seconds).","type":"number","format":"double"},"out_bytes":{"description":"Bytes of answers sent back (metered; the free plan includes plan.included_out_gb).","type":"integer","format":"int64"},"stored_bytes":{"type":"integer","format":"int64"},"gb_months":{"description":"Storage so far this period.","type":"number","format":"double"},"plan":{"$ref":"#/components/schemas/Plan"},"bill":{"description":"What this period costs so far, in dollars.","$ref":"#/components/schemas/Bill"}},"required":["period_start","month","queries","writes","write_bytes","docs_written","store_gets","store_bytes","mcp_queries","mcp_writes","embed_tokens","cpu_ms","out_bytes","stored_bytes","gb_months","plan","bill"]},"UsageDay":{"type":"object","properties":{"day":{"type":"string"},"queries":{"type":"integer","format":"int64"},"writes":{"type":"integer","format":"int64"},"write_bytes":{"type":"integer","format":"int64"},"docs_written":{"type":"integer","format":"int64"},"store_gets":{"type":"integer","format":"int64"},"store_bytes":{"type":"integer","format":"int64"},"mcp_queries":{"type":"integer","format":"int64"},"mcp_writes":{"type":"integer","format":"int64"},"embed_tokens":{"type":"integer","format":"int64"},"cpu_ms":{"type":"number","format":"double"},"out_bytes":{"type":"integer","format":"int64"},"gb_months":{"type":"number","format":"double"}},"required":["day","queries","writes","write_bytes","docs_written","store_gets","store_bytes","mcp_queries","mcp_writes","embed_tokens","cpu_ms","out_bytes","gb_months"]},"VectorConfig":{"type":"object","properties":{"dims":{"type":"integer","minimum":1,"maximum":2048,"description":"Your embeddings' length."},"metric":{"type":"string","enum":["cosine","euclidean"],"default":"cosine"}},"required":["dims"]},"WarmRequest":{"type":"object","properties":{"queries":{"type":"array","maxItems":20,"items":{"type":"string"},"description":"Searches about to be made: their document lists are fetched now."},"regex":{"type":"boolean","default":false}}},"WarmResult":{"type":"object","properties":{"namespace":{"type":"string"},"temperature":{"description":"Before warming: warm, local or cold.","type":"string"},"store_gets":{"type":"integer","format":"int64"},"store_bytes":{"type":"integer","format":"int64"},"took_ms":{"type":"number","format":"double"}},"required":["namespace","temperature","store_gets","store_bytes","took_ms"]},"WriteRequest":{"type":"object","properties":{"docs":{"type":"array","items":{"$ref":"#/components/schemas/Document"},"description":"Documents to put (insert or replace)."},"deletes":{"type":"array","items":{"type":"string"},"description":"Ids to delete."},"wait":{"type":"boolean","default":false,"description":"Answer only once the write is searchable on this server (it is durable either way)."}},"description":"1 to 10,000 documents and deletes a request, 64 MB at most."},"WriteResult":{"type":"object","properties":{"created":{"type":"boolean","description":"The namespace didn't exist: this write made it (with vectors of the first vector's length, cosine, when the documents have vectors; else text only). Check it to catch a mistyped name."},"written":{"type":"integer","format":"int64"},"deleted":{"type":"integer","format":"int64"},"version":{"type":"integer","format":"int64"},"stored_bytes":{"type":"integer","format":"int64"}},"required":["written","deleted","version","stored_bytes"]}},"responses":{"BadRequest":{"description":"Malformed: not the JSON expected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error": "expected a JSON body: {\"name\": \"...\"}", "code": "bad_request"}}}},"Unauthorized":{"description":"No key, or an unknown or revoked one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error": "send an API key (Authorization: Bearer tb_...) or sign in", "code": "unauthorized"}}}},"PaymentRequired":{"description":"The free plan's included usage this period is used up (queries, query CPU, object-storage reads, bytes returned, writes or storage; the message says which), or its namespace count or daily namespace creations are reached (code namespace_limit): upgrade to pro.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error": "this month's queries on the free plan are used up: upgrade to pro", "code": "payment_required"}}}},"Forbidden":{"description":"The key's scope or namespaces don't allow it, or the tenant is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error": "this key can only read: it may not do this", "code": "forbidden"}}}},"NotFound":{"description":"No such namespace, document, key or tenant (or one this key may not see: the same answer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error": "no such namespace", "code": "not_found"}}}},"Conflict":{"description":"Exists already, or not possible in this state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error": "you already have a namespace with that name", "code": "conflict"}}}},"PayloadTooLarge":{"description":"Too big: a request's documents (64 MB), an import (2 GB), or what a SQL query would read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error": "a request takes at most 64000000 bytes of documents", "code": "too_large"}}}},"Unprocessable":{"description":"Invalid: the message says why (code invalid with fields: which input fields; query_timeout: past the plan's time limit; scan_refused: send scan: true).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error": "a namespace's name is 1 to 64 of a-z, 0-9, '-', '_' and '.'", "code": "invalid"}}}},"TooManyRequests":{"description":"Over the tenant's or the key's rate, concurrency or cap, or writes arriving faster than the namespace commits them (code write_busy): retry after Retry-After seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error": "this tenant's requests are over its limit (100 a second): retry in a second", "code": "rate_limited"}}}},"NotImplemented":{"description":"Billing isn't set up on this server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error": "billing isn't set up on this server", "code": "not_implemented"}}}},"BadGateway":{"description":"Billing (Stripe) is unavailable right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error": "billing is unavailable right now", "code": "bad_gateway"}}}},"Unavailable":{"description":"Object storage failed: retry (nothing was written, unless the code is write_unknown: then it may have been; retrying a write is safe).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error": "storage failed: retry", "code": "unavailable"}}}},"InsufficientStorage":{"description":"The tenant's storage quota is full.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error": "this tenant's storage quota is full: delete documents or namespaces, or ask for a bigger quota", "code": "quota_full"}}}}},"headers":{"RetryAfter":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimitLimit":{"description":"Requests allowed in the current window (the tighter of the tenant's rate and the key's own, on this server).","schema":{"type":"integer"}},"RateLimitRemaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"RateLimitReset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"RateLimitPolicy":{"description":"The limit and its window in seconds, e.g. 100;w=1.","schema":{"type":"string"}}},"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"An API key (tb_...), or a login token from POST /v1/login. A key may also be sent as X-API-Key: tb_... (not declared as a second scheme: generated clients would then send the key twice)."}}},"security":[{"bearer":[]}]}