{
  "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.** A search or count reads one namespace (SQL can join several): put together what you search 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://docs.skimmerdb.com/guides/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, across all servers (shared every 250 ms, approximate): 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": "Views",
      "description": "SQL queries kept as per-segment partials, read as tables by name."
    },
    {
      "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 and views, 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,
                    "included_gb_months": 1,
                    "included_write_gb": 1,
                    "included_queries": 100000,
                    "included_writes": 100000,
                    "included_embed_tokens": 0,
                    "per_gb_month": 0,
                    "per_gb_written": 0,
                    "per_million_queries": 0,
                    "per_million_embed_tokens": 0,
                    "per_million_store_gets": 0,
                    "hard_limit": true,
                    "qps": 100,
                    "concurrency": 16,
                    "storage_quota_gb": 1,
                    "read_inflight_mb": 64,
                    "sql_seconds": 10,
                    "sql_concurrency": 2,
                    "query_seconds": 10,
                    "max_namespaces": 1000,
                    "included_cpu_seconds": 3600,
                    "included_store_gets": 1000000,
                    "included_out_gb": 10,
                    "max_creates_day": 1000,
                    "unverified_share": 0.01,
                    "export_seconds": 60,
                    "export_concurrency": 1,
                    "exports_day": 10,
                    "export_max_gb": 1,
                    "max_views": 20
                  },
                  "bill": {
                    "base": 0,
                    "storage": 0,
                    "writes": 0,
                    "queries": 0,
                    "embedding": 0,
                    "reads": 0,
                    "usage": 0,
                    "total": 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). With Content-Type application/x-ndjson the body is one document a line (up to 16 MB; any length: POST .../docs/stream).",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The namespace's name."
          },
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "NDJSON bodies: answer once the last batch is searchable."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Makes a retry safe: the same key and request within 24 hours is done once, and later tries get the first answer (a different request with the key: 422 idempotency_mismatch; while the first runs: 409)."
          }
        ],
        "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
                        ]
                      }
                    ]
                  }
                },
                "conditional": {
                  "summary": "Only if unchanged since read (else 409, nothing written)",
                  "value": {
                    "wait": true,
                    "docs": [
                      {
                        "id": "t-1",
                        "text": "Refund issued for order ACME-1042",
                        "if_version": "1761210000123:0"
                      },
                      {
                        "id": "t-9",
                        "text": "New ticket",
                        "if_absent": true
                      }
                    ]
                  }
                },
                "put_and_delete": {
                  "summary": "Put one, delete one",
                  "value": {
                    "docs": [
                      {
                        "id": "t-3",
                        "text": "Webhook timeouts since Tuesday",
                        "meta": {
                          "team": "api"
                        }
                      }
                    ],
                    "deletes": [
                      "t-2"
                    ]
                  }
                }
              }
            },
            "application/x-ndjson": {
              "schema": {
                "type": "string",
                "description": "One document a line, as in docs."
              }
            }
          }
        },
        "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"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "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."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Makes a retry safe: the same key and request within 24 hours is done once, and later tries get the first answer (a different request with the key: 422 idempotency_mismatch; while the first runs: 409)."
          }
        ],
        "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"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "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}/docs/stream": {
      "post": {
        "operationId": "writeDocumentStream",
        "summary": "Stream documents in (NDJSON)",
        "tags": [
          "Documents"
        ],
        "description": "Any number of documents in one request: one a line ({\"id\", \"text\", \"meta\", \"vector\"}, as in docs), read as it arrives and written in batches of 50,000 documents (or 16 MB) through the ordinary write path (limits, embedding, metering; the namespace made on the first batch). When the namespace's log can't keep up the stream is slowed (the body isn't read), not refused. Each batch is atomic, the stream isn't: an error stops it and the answer (or the error, with written) says how many were written before it; writes are by id, so sending it again is safe. Memory on the server: one batch. Metered as documents written and bytes written (see Usage).",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The namespace's name."
          },
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Answer once the last batch is searchable."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-ndjson": {
              "schema": {
                "type": "string",
                "description": "One document a line."
              },
              "examples": {
                "lines": {
                  "summary": "Two documents",
                  "value": "{\"id\": \"t-1\", \"text\": \"Refund requested\", \"meta\": {\"status\": \"open\"}}\n{\"id\": \"t-2\", \"text\": \"Login fails\", \"meta\": {\"status\": \"closed\"}}\n"
                }
              }
            }
          }
        },
        "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": 120000,
                  "deleted": 0,
                  "version": 9,
                  "stored_bytes": 9817221,
                  "batches": 3,
                  "lines": 120000,
                  "created": false,
                  "took_ms": 2410
                }
              }
            }
          },
          "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}/delete": {
      "post": {
        "operationId": "deleteDocuments",
        "summary": "Delete documents",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The namespace's name."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Makes a retry safe: the same key and request within 24 hours is done once, and later tries get the first answer (a different request with the key: 422 idempotency_mismatch; while the first runs: 409)."
          }
        ],
        "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"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "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, or fetched from ?url= (https only: a presigned S3 / GCS URL or any public server; private, loopback, link-local and metadata addresses are refused, after redirects too), up to 2 GB, parsed straight into columns, in segments of up to 1M rows: 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)."
          },
          {
            "name": "url",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uri"
            },
            "description": "Fetch the file from this https URL (presigned S3 / GCS, or any public server) instead of the body."
          }
        ],
        "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}/import/bucket": {
      "post": {
        "operationId": "importBucket",
        "summary": "Import files from your bucket",
        "tags": [
          "Documents"
        ],
        "description": "CSV, NDJSON or Parquet files read straight from your own bucket: AWS S3, or an S3-compatible service (Cloudflare R2, Backblaze B2, MinIO, Wasabi, Google Cloud Storage) given as s3.endpoint. One key, every file under a prefix, or a pattern (*.parquet), in key order: up to 100 files and 4 GB a request, each file up to 2 GB; next says where to go on (pass it as after). Each file is an import of its own (all its rows visible together); a failure stops the request and answers what was imported, the failing key (failed) and next. The keys are used for this request only, never stored; the endpoint's host must resolve to a public address. A namespace that doesn't exist is made (text only; created: true). Metered as writes of the documents and the files' bytes.",
        "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/BucketImportRequest"
              },
              "examples": {
                "r2": {
                  "summary": "Every Parquet file under a prefix in Cloudflare R2",
                  "value": {
                    "path": "s3://events/2026/10/*.parquet",
                    "id": "event_id",
                    "text": [
                      "message"
                    ],
                    "s3": {
                      "endpoint": "https://ACCOUNT_ID.r2.cloudflarestorage.com",
                      "region": "auto",
                      "access_key_id": "...",
                      "secret_access_key": "..."
                    }
                  }
                },
                "aws": {
                  "summary": "One CSV file in AWS S3",
                  "value": {
                    "path": "s3://acme-data/tickets.csv",
                    "id": "id",
                    "s3": {
                      "region": "us-east-1",
                      "access_key_id": "AKIA...",
                      "secret_access_key": "..."
                    }
                  }
                }
              }
            }
          }
        },
        "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/BucketImportResult"
                },
                "example": {
                  "files": [
                    {
                      "key": "2026/10/01.parquet",
                      "bytes": 48210331,
                      "rows": 1000000,
                      "written": 1000000
                    }
                  ],
                  "written": 1000000,
                  "rows": 1000000,
                  "bytes": 48210331,
                  "next": "",
                  "took_ms": 2140,
                  "created": true
                }
              }
            }
          },
          "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}/lake/sync": {
      "post": {
        "operationId": "syncLake",
        "summary": "Sync a DuckLake table",
        "tags": [
          "Documents"
        ],
        "description": "Keeps a table of your DuckLake (catalog in Postgres, data in your S3 bucket on AWS or an S3-compatible service such as R2 or MinIO) in the namespace, nothing copied by you: every live row a document (its id the key column's value; its text the text columns; its metadata the other columns and _rowid, the DuckLake row id). Each sync reads the catalog at a snapshot and writes only what changed since the snapshot synced before (inserts, updates, deletes, inlined rows, compactions); a change to the table's columns resyncs it whole. Run it after the lake's commits. The catalog URL and S3 keys are used for the sync only and never stored; the catalog's host must resolve to a public address. A namespace that doesn't exist is made (text only; created: true). Metered as writes of the documents upserted and deleted.",
        "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/LakeSyncRequest"
              },
              "examples": {
                "sync": {
                  "summary": "The latest snapshot, keyed by id",
                  "value": {
                    "catalog": "postgres://lake:secret@lake-catalog.example.com:5432/lake?sslmode=require",
                    "table": "main.tickets",
                    "text": [
                      "subject",
                      "body"
                    ],
                    "s3": {
                      "region": "us-east-1",
                      "access_key_id": "AKIA...",
                      "secret_access_key": "..."
                    }
                  }
                }
              }
            }
          }
        },
        "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/LakeSyncResult"
                },
                "example": {
                  "snapshot": 42,
                  "previous": 40,
                  "full": false,
                  "upserted": 120,
                  "deleted": 3,
                  "key": "id",
                  "skipped": [],
                  "took_ms": 380,
                  "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"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "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; with rank bm25, the documents containing any of q's words, best first by BM25 relevance; or, with vector, the nearest documents (alone, hybrid with q and filter, or with rank bm25 fused with the BM25 ranking). 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
                  }
                },
                "ranked": {
                  "summary": "Ranked by BM25 relevance",
                  "value": {
                    "q": "refund late delivery",
                    "rank": "bm25",
                    "filter": {
                      "status": "open"
                    },
                    "limit": 10
                  }
                },
                "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.98
                    ],
                    "limit": 10
                  }
                },
                "hybrid": {
                  "summary": "Nearest among those matching text and a filter",
                  "value": {
                    "vector": [
                      0.1,
                      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."
          },
          {
            "name": "rank",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "bm25"
              ]
            },
            "description": "bm25: the documents containing any of q's words, best first."
          }
        ],
        "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. A query over the namespace's documents as the table docs; the tenant's other namespaces are tables too, by name (quoted when it has - or .: \"web-logs\"), so it can join them. A key limited to some namespaces sees no others. Columns: id and every top-level meta key, typed from the values: int (booleans 1/0), float, text, date or time; missing is NULL; SHOW COLUMNS lists a namespace's. contains(text, '...') and grep(text, 'regex') go through the text index (in WHERE, on one table: contains(e.text, ...) when there are several). SELECT, WITH, UNION / INTERSECT / EXCEPT [ALL]; joins (INNER, LEFT, RIGHT, FULL, CROSS, USING, NATURAL); subqueries (IN, EXISTS, scalar, in FROM; correlated as [NOT] EXISTS / IN with = conditions); GROUP BY (ROLLUP, CUBE, GROUPING SETS), HAVING, QUALIFY, DISTINCT, ORDER BY, LIMIT / OFFSET; window functions (ROW_NUMBER, RANK, DENSE_RANK, NTILE, PERCENT_RANK, CUME_DIST, LAG, LEAD, FIRST_VALUE, LAST_VALUE, NTH_VALUE, aggregates OVER (PARTITION BY ... ORDER BY ... ROWS BETWEEN ...)); aggregates COUNT, COUNT(DISTINCT), SUM, AVG, MIN, MAX, STDDEV, VARIANCE, MEDIAN, QUANTILE, PERCENTILE_CONT / _DISC, MODE, STRING_AGG, ARG_MIN / ARG_MAX, BOOL_AND / BOOL_OR, COUNT_IF, CORR, with FILTER (WHERE ...); + - * / (a float; NULL dividing by 0) // %; CASE, COALESCE, NULLIF, CAST / TRY_CAST, text, math and date functions (LOWER, SUBSTRING, CONCAT, ||, REPLACE, SPLIT_PART, REGEXP_*, ROUND, EXTRACT, DATE_TRUNC, DATE_DIFF, INTERVAL, STRFTIME, ...). LENGTH counts bytes, CHAR_LENGTH characters. A query reads at most 16 tables; JSON answers stop at 10,000 rows (truncated): format=ndjson or csv (or Accept: application/x-ndjson / text/csv) streams every row instead (a plain scan of one namespace any number, window by window; other shapes up to their row cap, then a last too_many_rows line and a broken transfer), and POST /v1/sql/exports writes an answer to a file. Writes (write scope): INSERT INTO ns [(columns)] VALUES (...) | SELECT ..., CREATE TABLE ns AS SELECT ..., DELETE FROM ns [WHERE ...]; a row's id column names the document (an existing id is replaced), its text column is its text, the rest metadata; each statement is one commit (all rows or none; a DELETE removes at most 1,000,000), metered as writes, and the answer has dml. Reference: https://docs.skimmerdb.com/reference/sql. 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. One query is metered, however many namespaces it reads. Counts as one query (metered; see Usage).",
        "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": [
                "json",
                "ndjson",
                "csv"
              ],
              "default": "json"
            },
            "description": "ndjson or csv: every row streamed as it's made (no 10,000-row cap; Accept: application/x-ndjson or text/csv does the same). Its types come in X-Skimmerdb-Types. An error before the first row is the usual JSON error; after it, NDJSON gets a last error line and the transfer is broken off (never a short answer that looks whole)."
          }
        ],
        "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"
                  }
                },
                "join": {
                  "summary": "Join another namespace",
                  "value": {
                    "sql": "SELECT u.plan, COUNT(*) AS tickets FROM docs t JOIN users u ON u.id = t.user_id WHERE contains(t.text, 'refund') GROUP BY u.plan ORDER BY tickets DESC"
                  }
                },
                "window": {
                  "summary": "The latest document of each user",
                  "value": {
                    "sql": "SELECT id, user_id, ts FROM docs QUALIFY ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY ts DESC) = 1"
                  }
                },
                "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
                    ]
                  ],
                  "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
                }
              },
              "application/x-ndjson": {
                "schema": {
                  "type": "string",
                  "description": "format=ndjson: one JSON object a row (column: value)."
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "format=csv: a header line, then a line a row (RFC 4180; NULL is an empty field, empty text two quotes)."
                }
              }
            }
          },
          "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"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "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": "query",
        "x-beta": true
      },
      "get": {
        "operationId": "querySqlGet",
        "summary": "SQL over a namespace, by URL (beta)",
        "tags": [
          "SQL (beta)"
        ],
        "description": "Beta. As POST /v1/namespaces/{name}/sql. The same query as a GET, for clients that can only fetch a URL: DuckDB's read_csv / read_json over https (with a CREATE SECRET of TYPE http and BEARER_TOKEN), curl, a spreadsheet's import. The SQL goes in ?sql= (URL-encoded; DuckDB reads a * in a URL as a glob: write %2A). Reads only: SQL that writes answers 405.",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The namespace's name."
          },
          {
            "name": "sql",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The query, URL-encoded."
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "ndjson",
                "csv"
              ],
              "default": "json"
            },
            "description": "ndjson or csv: every row streamed as it's made (no 10,000-row cap; Accept: application/x-ndjson or text/csv does the same). Its types come in X-Skimmerdb-Types. An error before the first row is the usual JSON error; after it, NDJSON gets a last error line and the transfer is broken off (never a short answer that looks whole)."
          }
        ],
        "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"
                }
              },
              "application/x-ndjson": {
                "schema": {
                  "type": "string",
                  "description": "format=ndjson: one JSON object a row (column: value)."
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "format=csv: a header line, then a line a row (RFC 4180; NULL is an empty field, empty text two quotes)."
                }
              }
            }
          },
          "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"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "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/sql": {
      "post": {
        "operationId": "querySqlTenant",
        "summary": "SQL across namespaces (beta)",
        "tags": [
          "SQL (beta)"
        ],
        "description": "Beta. A query whose tables are the tenant's namespaces, each by name (quoted when it has - or .: \"web-logs\"); no table is docs here. A key limited to some namespaces sees only those: any other name is no table, like one nobody has. Columns: id and every top-level meta key, typed from the values: int (booleans 1/0), float, text, date or time; missing is NULL; SHOW COLUMNS lists a namespace's. contains(text, '...') and grep(text, 'regex') go through the text index (in WHERE, on one table: contains(e.text, ...) when there are several). SELECT, WITH, UNION / INTERSECT / EXCEPT [ALL]; joins (INNER, LEFT, RIGHT, FULL, CROSS, USING, NATURAL); subqueries (IN, EXISTS, scalar, in FROM; correlated as [NOT] EXISTS / IN with = conditions); GROUP BY (ROLLUP, CUBE, GROUPING SETS), HAVING, QUALIFY, DISTINCT, ORDER BY, LIMIT / OFFSET; window functions (ROW_NUMBER, RANK, DENSE_RANK, NTILE, PERCENT_RANK, CUME_DIST, LAG, LEAD, FIRST_VALUE, LAST_VALUE, NTH_VALUE, aggregates OVER (PARTITION BY ... ORDER BY ... ROWS BETWEEN ...)); aggregates COUNT, COUNT(DISTINCT), SUM, AVG, MIN, MAX, STDDEV, VARIANCE, MEDIAN, QUANTILE, PERCENTILE_CONT / _DISC, MODE, STRING_AGG, ARG_MIN / ARG_MAX, BOOL_AND / BOOL_OR, COUNT_IF, CORR, with FILTER (WHERE ...); + - * / (a float; NULL dividing by 0) // %; CASE, COALESCE, NULLIF, CAST / TRY_CAST, text, math and date functions (LOWER, SUBSTRING, CONCAT, ||, REPLACE, SPLIT_PART, REGEXP_*, ROUND, EXTRACT, DATE_TRUNC, DATE_DIFF, INTERVAL, STRFTIME, ...). LENGTH counts bytes, CHAR_LENGTH characters. A query reads at most 16 tables; JSON answers stop at 10,000 rows (truncated): format=ndjson or csv (or Accept: application/x-ndjson / text/csv) streams every row instead (a plain scan of one namespace any number, window by window; other shapes up to their row cap, then a last too_many_rows line and a broken transfer), and POST /v1/sql/exports writes an answer to a file. Writes (write scope): INSERT INTO ns [(columns)] VALUES (...) | SELECT ..., CREATE TABLE ns AS SELECT ..., DELETE FROM ns [WHERE ...]; a row's id column names the document (an existing id is replaced), its text column is its text, the rest metadata; each statement is one commit (all rows or none; a DELETE removes at most 1,000,000), metered as writes, and the answer has dml. Reference: https://docs.skimmerdb.com/reference/sql. 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. One query is metered, however many namespaces it reads. Counts as one query (metered; see Usage).",
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "ndjson",
                "csv"
              ],
              "default": "json"
            },
            "description": "ndjson or csv: every row streamed as it's made (no 10,000-row cap; Accept: application/x-ndjson or text/csv does the same). Its types come in X-Skimmerdb-Types. An error before the first row is the usual JSON error; after it, NDJSON gets a last error line and the transfer is broken off (never a short answer that looks whole)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SqlRequest"
              },
              "examples": {
                "join": {
                  "summary": "Events by the users' plan",
                  "value": {
                    "sql": "SELECT u.plan, COUNT(*) AS events, SUM(e.amount) AS total FROM \"web-events\" e JOIN users u ON u.id = e.user_id GROUP BY u.plan ORDER BY total DESC"
                  }
                },
                "missing": {
                  "summary": "Users with no event",
                  "value": {
                    "sql": "SELECT id FROM users u WHERE NOT EXISTS (SELECT 1 FROM \"web-events\" e WHERE e.user_id = u.id)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SqlResult"
                },
                "example": {
                  "namespace": "",
                  "columns": [
                    "plan",
                    "events",
                    "total"
                  ],
                  "types": [
                    "text",
                    "int",
                    "float"
                  ],
                  "rows": [
                    [
                      "pro",
                      3,
                      16
                    ]
                  ],
                  "chunks": 2,
                  "scanned": 2,
                  "partial": false,
                  "truncated": false,
                  "took_ms": 1.4,
                  "temperature": "warm",
                  "store_gets": 0,
                  "store_bytes": 0,
                  "cpu_ms": 0.9,
                  "timed_out": false
                }
              },
              "application/x-ndjson": {
                "schema": {
                  "type": "string",
                  "description": "format=ndjson: one JSON object a row (column: value)."
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "format=csv: a header line, then a line a row (RFC 4180; NULL is an empty field, empty text two quotes)."
                }
              }
            }
          },
          "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"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "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": "query",
        "x-beta": true
      },
      "get": {
        "operationId": "querySqlTenantGet",
        "summary": "SQL across namespaces, by URL (beta)",
        "tags": [
          "SQL (beta)"
        ],
        "description": "Beta. As POST /v1/sql. The same query as a GET, for clients that can only fetch a URL: DuckDB's read_csv / read_json over https (with a CREATE SECRET of TYPE http and BEARER_TOKEN), curl, a spreadsheet's import. The SQL goes in ?sql= (URL-encoded; DuckDB reads a * in a URL as a glob: write %2A). Reads only: SQL that writes answers 405.",
        "parameters": [
          {
            "name": "sql",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The query, URL-encoded."
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "ndjson",
                "csv"
              ],
              "default": "json"
            },
            "description": "ndjson or csv: every row streamed as it's made (no 10,000-row cap; Accept: application/x-ndjson or text/csv does the same). Its types come in X-Skimmerdb-Types. An error before the first row is the usual JSON error; after it, NDJSON gets a last error line and the transfer is broken off (never a short answer that looks whole)."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SqlResult"
                }
              },
              "application/x-ndjson": {
                "schema": {
                  "type": "string",
                  "description": "format=ndjson: one JSON object a row (column: value)."
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "format=csv: a header line, then a line a row (RFC 4180; NULL is an empty field, empty text two quotes)."
                }
              }
            }
          },
          "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"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "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}/views": {
      "post": {
        "operationId": "createView",
        "summary": "Create a view",
        "tags": [
          "Views"
        ],
        "description": "A view is a SQL query over one namespace (FROM docs) whose answer is kept as partials per segment: COUNT, SUM, MIN, MAX, AVG, COUNT(DISTINCT ...), COUNT_IF and f(...) FILTER (WHERE ...) over any WHERE, GROUP BY any expressions (DATE_TRUNC buckets, CASE, ...), with HAVING, ORDER BY and LIMIT applied on top. Name it as a table in SQL (SELECT * FROM daily_signups, or join it). It is always exact: a segment's partial is reused only while that segment's visible rows are the ones it was computed from, so a write or delete recomputes only the segments it touched. Views go with their namespace. Its name is the tenant's (not a namespace's name, not docs). Creating one runs it once (metered as a query) and answers its columns. A query that can't run from parts is refused with 422 view_not_decomposable and the reason; past the plan's views, 402 view_limit.",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The namespace's name."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "1 to 64 of a-z, 0-9, '-', '_' and '.'"
                  },
                  "sql": {
                    "type": "string",
                    "description": "The query, over docs."
                  }
                },
                "required": [
                  "name",
                  "sql"
                ]
              },
              "examples": {
                "daily": {
                  "summary": "Signups a day by plan",
                  "value": {
                    "name": "daily_signups",
                    "sql": "SELECT DATE_TRUNC('day', ts) AS day, plan, COUNT(*) AS signups FROM docs WHERE kind = 'signup' GROUP BY 1, 2"
                  }
                }
              }
            }
          }
        },
        "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/ViewCreated"
                },
                "example": {
                  "name": "daily_signups",
                  "namespace": "events",
                  "sql": "SELECT DATE_TRUNC('day', ts) AS day, plan, COUNT(*) AS signups FROM docs WHERE kind = 'signup' GROUP BY 1, 2",
                  "created_at": "2026-10-18T09:00:00Z",
                  "columns": [
                    "day",
                    "plan",
                    "signups"
                  ],
                  "types": [
                    "int",
                    "text",
                    "int"
                  ]
                }
              }
            }
          },
          "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"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "x-metered": "query"
      },
      "get": {
        "operationId": "listViews",
        "summary": "List a namespace's views",
        "tags": [
          "Views"
        ],
        "description": "A view is a SQL query over one namespace (FROM docs) whose answer is kept as partials per segment: COUNT, SUM, MIN, MAX, AVG, COUNT(DISTINCT ...), COUNT_IF and f(...) FILTER (WHERE ...) over any WHERE, GROUP BY any expressions (DATE_TRUNC buckets, CASE, ...), with HAVING, ORDER BY and LIMIT applied on top. Name it as a table in SQL (SELECT * FROM daily_signups, or join it). It is always exact: a segment's partial is reused only while that segment's visible rows are the ones it was computed from, so a write or delete recomputes only the segments it touched. Views go with their namespace. ",
        "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/ViewList"
                },
                "example": {
                  "views": [
                    {
                      "name": "daily_signups",
                      "namespace": "events",
                      "sql": "SELECT DATE_TRUNC('day', ts) AS day, COUNT(*) AS signups FROM docs GROUP BY 1",
                      "created_at": "2026-10-18T09:00:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/namespaces/{name}/views/{view}": {
      "get": {
        "operationId": "getView",
        "summary": "Get a view",
        "tags": [
          "Views"
        ],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The namespace's name."
          },
          {
            "name": "view",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "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/View"
                },
                "example": {
                  "name": "daily_signups",
                  "namespace": "events",
                  "sql": "SELECT DATE_TRUNC('day', ts) AS day, COUNT(*) AS signups FROM docs GROUP BY 1",
                  "created_at": "2026-10-18T09:00:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "deleteView",
        "summary": "Delete a view",
        "tags": [
          "Views"
        ],
        "description": "Its kept partials go with the segments they describe.",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The namespace's name."
          },
          {
            "name": "view",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "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/sql/exports": {
      "post": {
        "operationId": "createExport",
        "summary": "Export a query's answer (beta)",
        "tags": [
          "SQL (beta)"
        ],
        "description": "For answers too big or too slow for a request: the query runs in the background (the plan's export_seconds: 60 s free, 900 s pro) and its rows are written as CSV, NDJSON or Parquet into object storage under the tenant's own prefix, kept 24 hours, or, with destination, delivered to the tenant's own bucket (AWS S3, R2, B2, MinIO, ...) by multipart upload and not kept here (its bytes count as bytes returned). A plain scan of one namespace exports any number of rows; other shapes up to their row cap. Poll GET /v1/sql/exports/{id}; download when done. Limits: exports queued or running at once (1 free, 2 pro; 429), exports a UTC day (10 free; 402 export_limit), an export's size (1 GB free, 50 GB pro; failed past it). Metered: one query when it runs; downloads as bytes returned. A server that restarts fails its running exports (run them again).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExportRequest"
              },
              "examples": {
                "csv": {
                  "summary": "Every open ticket as CSV",
                  "value": {
                    "sql": "SELECT id, status, priority, opened_at FROM tickets WHERE status = 'open'",
                    "format": "csv"
                  }
                },
                "r2": {
                  "summary": "As Parquet, delivered to Cloudflare R2",
                  "value": {
                    "sql": "SELECT * FROM events WHERE day >= '2026-10-01'",
                    "format": "parquet",
                    "destination": {
                      "path": "s3://analytics/exports/events-october.parquet",
                      "s3": {
                        "endpoint": "https://ACCOUNT_ID.r2.cloudflarestorage.com",
                        "region": "auto",
                        "access_key_id": "...",
                        "secret_access_key": "..."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Export"
                },
                "example": {
                  "id": "ex_5f0c1a2b3c4d5e6f7a8b",
                  "status": "done",
                  "format": "csv",
                  "sql": "SELECT id, status FROM tickets",
                  "rows": 120000,
                  "bytes": 3400211,
                  "error": "",
                  "created_at": "2026-10-18T10:00:00Z",
                  "finished_at": "2026-10-18T10:00:04Z",
                  "expires_at": "2026-10-19T10:00:00Z",
                  "download": "/v1/sql/exports/ex_5f0c1a2b3c4d5e6f7a8b/download"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "x-metered": "query",
        "x-beta": true
      },
      "get": {
        "operationId": "listExports",
        "summary": "List exports (beta)",
        "tags": [
          "SQL (beta)"
        ],
        "description": "The tenant's exports not expired, newest first (the last 100). A key limited to namespaces sees only its own.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportList"
                },
                "example": {
                  "exports": [
                    {
                      "id": "ex_5f0c1a2b3c4d5e6f7a8b",
                      "status": "done",
                      "format": "csv",
                      "sql": "SELECT id, status FROM tickets",
                      "rows": 120000,
                      "bytes": 3400211,
                      "error": "",
                      "created_at": "2026-10-18T10:00:00Z",
                      "finished_at": "2026-10-18T10:00:04Z",
                      "expires_at": "2026-10-19T10:00:00Z",
                      "download": "/v1/sql/exports/ex_5f0c1a2b3c4d5e6f7a8b/download"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-beta": true
      }
    },
    "/v1/sql/exports/{id}": {
      "get": {
        "operationId": "getExport",
        "summary": "An export's status (beta)",
        "tags": [
          "SQL (beta)"
        ],
        "description": "queued, running (rows and bytes so far), done (download), failed (error) or expired.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Export"
                },
                "example": {
                  "id": "ex_5f0c1a2b3c4d5e6f7a8b",
                  "status": "done",
                  "format": "csv",
                  "sql": "SELECT id, status FROM tickets",
                  "rows": 120000,
                  "bytes": 3400211,
                  "error": "",
                  "created_at": "2026-10-18T10:00:00Z",
                  "finished_at": "2026-10-18T10:00:04Z",
                  "expires_at": "2026-10-19T10:00:00Z",
                  "download": "/v1/sql/exports/ex_5f0c1a2b3c4d5e6f7a8b/download"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-beta": true
      }
    },
    "/v1/sql/exports/{id}/download": {
      "get": {
        "operationId": "downloadExport",
        "summary": "Download an export (beta)",
        "tags": [
          "SQL (beta)"
        ],
        "description": "The file, streamed (Content-Type text/csv, application/x-ndjson or application/vnd.apache.parquet; X-Skimmerdb-Size: its size). Resumable: Range: bytes=first-last (one range) answers 206 with Content-Range. Metered as bytes returned; on the free plan a download stops when they're used up (a range past them: 402).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Range",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "bytes=first-last, bytes=first- or bytes=-suffix: part of the file (206)."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/x-ndjson": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/vnd.apache.parquet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "410": {
            "$ref": "#/components/responses/Gone"
          },
          "416": {
            "$ref": "#/components/responses/RangeNotSatisfiable"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "x-beta": true
      }
    },
    "/v1/namespaces/{name}/names": {
      "post": {
        "operationId": "linkedNames",
        "summary": "Ids by segment and number",
        "tags": [
          "Linked namespaces"
        ],
        "description": "A linked namespace's document ids by place: each [segment, number] (as a linked search's hits give them in seg and n) read back as the document's id, \"\" for one that isn't there. Up to 10,000 a request. One query for limits and metering.",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The namespace's name."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "docs": {
                    "type": "array",
                    "items": {
                      "type": "array",
                      "prefixItems": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "integer"
                        }
                      ],
                      "minItems": 2,
                      "maxItems": 2
                    },
                    "maxItems": 10000
                  }
                },
                "required": [
                  "docs"
                ]
              },
              "examples": {
                "two": {
                  "summary": "Two documents",
                  "value": {
                    "docs": [
                      [
                        "seg-000123",
                        5
                      ],
                      [
                        "seg-000124",
                        0
                      ]
                    ]
                  }
                }
              }
            }
          }
        },
        "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": {
                  "type": "object",
                  "properties": {
                    "namespace": {
                      "type": "string"
                    },
                    "names": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "namespace",
                    "names"
                  ]
                }
              }
            }
          },
          "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"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "x-metered": "query"
      }
    },
    "/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, create_view, list_views, delete_view, 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"
        ]
      },
      "BucketImportFile": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string"
          },
          "bytes": {
            "type": "integer",
            "format": "int64"
          },
          "rows": {
            "type": "integer",
            "format": "int64"
          },
          "written": {
            "type": "integer",
            "format": "int64"
          }
        },
        "required": [
          "key",
          "bytes",
          "rows",
          "written"
        ]
      },
      "BucketImportRequest": {
        "type": "object",
        "required": [
          "path",
          "s3"
        ],
        "properties": {
          "path": {
            "type": "string",
            "description": "s3://bucket/key (one file), s3://bucket/prefix/ (every file under it) or s3://bucket/prefix/*.parquet (those ending as the pattern after the *)."
          },
          "format": {
            "type": "string",
            "enum": [
              "",
              "csv",
              "ndjson",
              "parquet"
            ],
            "default": "",
            "description": "Empty: from each file's extension (.csv, .ndjson / .jsonl / .json, .parquet)."
          },
          "id": {
            "type": "string",
            "description": "The column holding document ids (default: row numbers)."
          },
          "text": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Columns to index as text."
          },
          "schema": {
            "type": "string",
            "description": "CSV: a CREATE TABLE giving the columns' types."
          },
          "after": {
            "type": "string",
            "default": "",
            "description": "Only keys after this one (a previous answer's next)."
          },
          "s3": {
            "$ref": "#/components/schemas/S3Keys"
          }
        },
        "description": "Files in the tenant's bucket to import."
      },
      "BucketImportResult": {
        "type": "object",
        "properties": {
          "files": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BucketImportFile"
            },
            "description": "The files imported, in order (each committed on its own)."
          },
          "written": {
            "type": "integer",
            "format": "int64"
          },
          "rows": {
            "type": "integer",
            "format": "int64"
          },
          "bytes": {
            "type": "integer",
            "format": "int64"
          },
          "next": {
            "type": "string",
            "description": "More files remain: pass this as after. Empty: done."
          },
          "took_ms": {
            "type": "number"
          },
          "created": {
            "type": "boolean",
            "description": "The namespace didn't exist: this import made it (text only)."
          }
        },
        "required": [
          "files",
          "written",
          "rows",
          "bytes",
          "next",
          "took_ms",
          "created"
        ]
      },
      "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"
            }
          },
          "if_versions": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "string",
                "null"
              ]
            },
            "description": "More conditions, by id (deletes' too): the version it must still be at, or null: it must not exist. Any that doesn't hold: nothing is written (409 version_conflict)."
          },
          "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)."
          },
          "if_version": {
            "type": "string",
            "description": "Write only if this document is still at this version (a read's version): otherwise nothing in the request is written (409 version_conflict)."
          },
          "if_absent": {
            "type": "boolean",
            "default": false,
            "description": "Write only if no document has this id yet."
          }
        },
        "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), gone (410), too_large (413), range_not_satisfiable (416), invalid (422; fields says which), rate_limited (429), internal (500), not_implemented (501), bad_gateway (502), unavailable (503), quota_full (507). More precise ones: version_conflict (409: a write condition didn't hold, nothing written; conflicts says which), idempotency_in_progress (409: retry shortly), idempotency_mismatch (422: the key was used for a different request), 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), out_budget (a streamed answer's last NDJSON line: the free plan's bytes returned ran out), too_many_rows (a streamed answer's last NDJSON line: a held answer passed its row cap), export_limit (402: the plan's exports a day), write_unknown (503: the write may or may not have been applied; retrying is safe), disk_busy (503: an import found the server short of local disk; nothing was written, retry after retry_after), 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), view_not_decomposable (422: a view's query can't run from per-segment parts; the message says why), view_limit (402: the plan's view count; 422: a namespace's 20)."
          },
          "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)."
          },
          "conflicts": {
            "type": "array",
            "description": "version_conflict: each condition that didn't hold.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "expected": {
                  "type": "string",
                  "description": "The version the write asked for (empty: absent)."
                },
                "current": {
                  "type": "string",
                  "description": "Its version now (empty: absent; (being written): another write to it hasn't settled)."
                }
              },
              "required": [
                "id",
                "expected",
                "current"
              ]
            }
          }
        },
        "required": [
          "error",
          "code"
        ],
        "description": "Every error answer, whatever the status."
      },
      "Export": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "done",
              "failed",
              "expired"
            ]
          },
          "format": {
            "type": "string"
          },
          "sql": {
            "type": "string"
          },
          "rows": {
            "type": "integer",
            "format": "int64",
            "description": "Rows written so far (all, once done)."
          },
          "bytes": {
            "type": "integer",
            "format": "int64",
            "description": "The file's size so far (all, once done)."
          },
          "error": {
            "type": "string",
            "description": "failed: why."
          },
          "created_at": {
            "type": "string"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "expires_at": {
            "type": "string",
            "description": "24 hours after it was made: the file is deleted then."
          },
          "download": {
            "type": "string",
            "description": "done: where to download it."
          },
          "destination": {
            "type": "string",
            "description": "Delivered to the tenant's bucket: the file's s3:// path (no download here)."
          }
        },
        "required": [
          "id",
          "status",
          "format",
          "sql",
          "rows",
          "bytes",
          "error",
          "created_at",
          "finished_at",
          "expires_at"
        ]
      },
      "ExportDestination": {
        "type": "object",
        "required": [
          "path",
          "s3"
        ],
        "properties": {
          "path": {
            "type": "string",
            "description": "s3://bucket/key for the file; ending in / a name is made (export-<time>.<format>)."
          },
          "s3": {
            "$ref": "#/components/schemas/S3Keys"
          }
        },
        "description": "Deliver the file to the tenant's own bucket (AWS S3, R2, B2, MinIO, ...) by multipart upload instead of keeping it here. The keys are checked at once (a small object is written and deleted) and kept in memory only while the export runs."
      },
      "ExportList": {
        "type": "object",
        "properties": {
          "exports": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Export"
            }
          }
        },
        "required": [
          "exports"
        ]
      },
      "ExportRequest": {
        "type": "object",
        "properties": {
          "sql": {
            "type": "string",
            "description": "A query (as POST /v1/sql: tables are the tenant's namespaces by name)."
          },
          "format": {
            "type": "string",
            "enum": [
              "csv",
              "ndjson",
              "parquet"
            ],
            "default": "csv",
            "description": "CSV (a header line, RFC 4180), NDJSON (an object a row) or Parquet (one file: row groups of up to 131,072 rows, ZSTD; every column nullable; int as INT64, float DOUBLE, text STRING, date DATE, time TIMESTAMP (microseconds, not UTC-adjusted), decimal DECIMAL(18, s))."
          },
          "destination": {
            "$ref": "#/components/schemas/ExportDestination"
          }
        },
        "required": [
          "sql"
        ]
      },
      "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. rank bm25: the BM25 score (higher is better); with a vector too, the fused score (the sum of 1 / (60 + rank) over the two rankings)."
          },
          "vector_rank": {
            "type": "integer",
            "description": "rank bm25 with a vector: the document's place in the vector ranking (1 first; 0: not in it)."
          },
          "text_rank": {
            "type": "integer",
            "description": "rank bm25 with a vector: its place in the BM25 ranking (1 first; 0: not in it)."
          },
          "seg": {
            "type": "string",
            "description": "Linked namespaces: the source segment holding it (names reads ids by seg and n)."
          },
          "n": {
            "type": "integer",
            "description": "Linked namespaces: its number in seg."
          },
          "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 and views, 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)."
          }
        }
      },
      "LakeSyncRequest": {
        "type": "object",
        "required": [
          "catalog",
          "table",
          "s3"
        ],
        "properties": {
          "catalog": {
            "type": "string",
            "description": "The lake's catalog: a postgres:// URL (DuckLake's Postgres catalog), reachable at a public address. Used for this sync only, never stored."
          },
          "table": {
            "type": "string",
            "description": "The table: name (schema main) or schema.name."
          },
          "key": {
            "type": "string",
            "default": "",
            "description": "The column whose value is a document's id. Default: id when the table has one, else the DuckLake row id; rowid: the row id."
          },
          "text": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Columns whose values are the searchable text (default: every text column)."
          },
          "snapshot": {
            "type": "integer",
            "format": "int64",
            "default": -1,
            "description": "The snapshot to sync to (-1: the latest). Never older than the one synced."
          },
          "s3": {
            "type": "object",
            "required": [
              "region",
              "access_key_id",
              "secret_access_key"
            ],
            "properties": {
              "region": {
                "type": "string",
                "default": "us-east-1"
              },
              "access_key_id": {
                "type": "string"
              },
              "secret_access_key": {
                "type": "string"
              },
              "session_token": {
                "type": "string"
              },
              "endpoint": {
                "type": "string",
                "default": "",
                "description": "An S3-compatible service instead of AWS: an https URL of scheme, host and optional port, no path (requests are path-style). Cloudflare R2: <account>.r2.cloudflarestorage.com with region auto; also MinIO, Backblaze B2, Wasabi, Google Cloud Storage's XML API. Its host must resolve to a public address. Empty: AWS S3 in region."
              }
            },
            "description": "Keys that can read the lake's data_path (s3://...), on AWS or the endpoint given. Used for this sync only, never stored."
          }
        },
        "description": "A DuckLake table to keep in the namespace."
      },
      "LakeSyncResult": {
        "type": "object",
        "properties": {
          "snapshot": {
            "type": "integer",
            "format": "int64",
            "description": "The snapshot the namespace now holds."
          },
          "previous": {
            "type": "integer",
            "format": "int64",
            "description": "The snapshot it held before (-1: none)."
          },
          "full": {
            "type": "boolean",
            "description": "Every live row was written (the first sync, or the table's columns, key or text changed)."
          },
          "upserted": {
            "type": "integer",
            "format": "int64"
          },
          "deleted": {
            "type": "integer",
            "format": "int64"
          },
          "key": {
            "type": "string",
            "description": "The key column (empty: row ids)."
          },
          "skipped": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Columns not kept (nested types, blobs)."
          },
          "took_ms": {
            "type": "number"
          },
          "created": {
            "type": "boolean",
            "description": "The namespace didn't exist: this sync made it."
          }
        },
        "required": [
          "snapshot",
          "previous",
          "full",
          "upserted",
          "deleted",
          "key",
          "skipped",
          "took_ms",
          "created"
        ]
      },
      "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"
          },
          "max_views": {
            "type": "integer",
            "format": "int64"
          },
          "export_seconds": {
            "type": "integer",
            "format": "int64"
          },
          "export_concurrency": {
            "type": "integer",
            "format": "int64"
          },
          "exports_day": {
            "type": "integer",
            "format": "int64"
          },
          "export_max_gb": {
            "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",
          "max_views",
          "export_seconds",
          "export_concurrency",
          "exports_day",
          "export_max_gb"
        ]
      },
      "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"
        ]
      },
      "S3Keys": {
        "type": "object",
        "properties": {
          "region": {
            "type": "string",
            "default": "us-east-1",
            "description": "The bucket's region (auto for Cloudflare R2)."
          },
          "access_key_id": {
            "type": "string"
          },
          "secret_access_key": {
            "type": "string"
          },
          "session_token": {
            "type": "string"
          },
          "endpoint": {
            "type": "string",
            "default": "",
            "description": "An S3-compatible service instead of AWS: an https URL of scheme, host and optional port, no path (requests are path-style). Cloudflare R2: <account>.r2.cloudflarestorage.com with region auto; also Backblaze B2, MinIO, Wasabi, Google Cloud Storage's XML API (HMAC keys). Its host must resolve to a public address. Empty: AWS S3 in region."
          }
        },
        "description": "Keys for the tenant's own bucket, on AWS S3 or an S3-compatible service. Used while the request (or export) runs, never stored. Without access_key_id and secret_access_key, requests go unsigned: a public bucket (such as AWS Open Data), for reading only."
      },
      "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
          },
          "rank": {
            "type": "string",
            "enum": [
              "",
              "bm25"
            ],
            "default": "",
            "description": "bm25: the documents containing any of q's words (letters and digits, case ignored), best first by BM25 relevance, each with a score; next pages it. With vector (or semantic) too: the two rankings fused by reciprocal rank (no next). Empty: every match of q, newest first."
          },
          "order": {
            "type": "string",
            "enum": [
              "",
              "newest"
            ],
            "default": "",
            "description": "Linked namespaces: newest walks the newest segments first (empty: the source's order)."
          },
          "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), best first (rank bm25) 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 query (SELECT, WITH, UNION ...) whose tables are namespaces (docs: the one in the path), SHOW COLUMNS, or a write (write scope): INSERT INTO ns [(columns)] VALUES (...) | SELECT ..., CREATE TABLE ns AS SELECT ..., DELETE FROM ns [WHERE ...]."
          },
          "format": {
            "type": "string",
            "enum": [
              "json",
              "ndjson",
              "csv"
            ],
            "default": "json",
            "description": "json: the answer below (at most 10,000 rows). ndjson or csv: the rows streamed as they're made, with no row cap (as ?format= or an Accept header)."
          }
        },
        "required": [
          "sql"
        ]
      },
      "SqlResult": {
        "type": "object",
        "properties": {
          "namespace": {
            "type": "string",
            "description": "The namespace in the path (\"\" for /v1/sql)."
          },
          "columns": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "int, float, text, date, time, decimal (its values as strings, exact) or null (every value NULL), one a column."
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": [
                  "string",
                  "number",
                  "null"
                ]
              }
            }
          },
          "chunks": {
            "type": "integer",
            "format": "int64",
            "description": "Column blocks in the namespaces read."
          },
          "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"
          },
          "dml": {
            "$ref": "#/components/schemas/SqlWrite"
          },
          "partials": {
            "type": "object",
            "description": "Present when the query ran from per-segment partials (one namespace and aggregates that merge from parts, or a view): which view's partials answered it and how many segments' partials were computed for it or reused. Always exact.",
            "properties": {
              "view": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The named view read (several: comma-separated), or null."
              },
              "kind": {
                "type": "string",
                "enum": [
                  "named",
                  "auto",
                  "none"
                ],
                "description": "named: a view's kept partials; auto: an automatic view's (a query shape repeated on a big namespace); none: partials kept in memory only."
              },
              "computed": {
                "type": "integer",
                "description": "Segments whose partials were computed for this query."
              },
              "reused": {
                "type": "integer",
                "description": "Segments whose partials were reused (unchanged since computed)."
              },
              "remote": {
                "type": "integer",
                "description": "Of those computed, how many on other servers of the cluster."
              }
            },
            "required": [
              "view",
              "kind",
              "computed",
              "reused",
              "remote"
            ]
          },
          "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"
        ]
      },
      "SqlWrite": {
        "type": "object",
        "description": "What a SQL write did (INSERT, CREATE TABLE, DELETE).",
        "properties": {
          "statement": {
            "type": "string",
            "enum": [
              "insert",
              "create",
              "delete"
            ]
          },
          "namespace": {
            "type": "string",
            "description": "The namespace written."
          },
          "inserted": {
            "type": "integer",
            "format": "int64",
            "description": "Documents written (an id that existed is replaced)."
          },
          "deleted": {
            "type": "integer",
            "format": "int64"
          },
          "version": {
            "type": "integer",
            "format": "int64",
            "description": "The namespace's version with the write."
          },
          "created": {
            "type": "boolean",
            "description": "The statement made the namespace."
          }
        },
        "required": [
          "statement",
          "namespace",
          "inserted",
          "deleted",
          "version",
          "created"
        ]
      },
      "StoredDocument": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "meta": {
            "type": "object",
            "additionalProperties": true
          },
          "vector": {
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "version": {
            "type": "string",
            "description": "Where its current text was written (<write>:<position>): give it as if_version to change it only if nobody else has."
          },
          "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"
        ]
      },
      "View": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "namespace": {
            "type": "string"
          },
          "sql": {
            "type": "string"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "namespace",
          "sql",
          "created_at"
        ]
      },
      "ViewCreated": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "namespace": {
            "type": "string"
          },
          "sql": {
            "type": "string"
          },
          "created_at": {
            "type": "string"
          },
          "columns": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "types": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "name",
          "namespace",
          "sql",
          "created_at",
          "columns",
          "types"
        ]
      },
      "ViewList": {
        "type": "object",
        "properties": {
          "views": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/View"
            }
          }
        },
        "required": [
          "views"
        ]
      },
      "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."
          },
          "if_versions": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "string",
                "null"
              ]
            },
            "description": "More conditions, by id (deletes' too): the version it must still be at, or null: it must not exist. Any that doesn't hold: nothing is written (409 version_conflict)."
          },
          "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": {
          "took_ms": {
            "type": "integer",
            "format": "int64",
            "description": "NDJSON streams: milliseconds."
          },
          "lines": {
            "type": "integer",
            "format": "int64",
            "description": "NDJSON streams: lines read."
          },
          "batches": {
            "type": "integer",
            "format": "int64",
            "description": "NDJSON streams: batches written."
          },
          "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."
          },
          "versions": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "wait: true (up to 1,000 documents): each written document's new version, by id."
          },
          "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"
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "Not with this method: SQL that writes needs POST.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "SQL that writes (INSERT, CREATE TABLE AS, DELETE) needs POST",
              "code": "method_not_allowed"
            }
          }
        }
      },
      "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"
            }
          }
        }
      },
      "Gone": {
        "description": "It existed, but not anymore (an export past its 24 hours).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "this export expired (they're kept 24 hours): run it again",
              "code": "gone"
            }
          }
        }
      },
      "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"
            }
          }
        }
      },
      "RangeNotSatisfiable": {
        "description": "The Range asked for isn't in the file.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "range not satisfiable",
              "code": "range_not_satisfiable"
            }
          }
        }
      },
      "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).",
        "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": []
    }
  ]
}
