API conventions
Base path /api/v1. JSON only. Auth: Authorization: Bearer <token> (session token or
mn_pat_ PAT) or the session cookie (web). See authentication.
Resource layout
Section titled “Resource layout”GET /meGET|POST /workspacesGET|PATCH|DELETE /workspaces/:wsGET|POST /workspaces/:ws/spaces PATCH|DELETE /spaces/:spaceGET|POST /workspaces/:ws/members POST /workspaces/:ws/invitesGET|POST /workspaces/:ws/databases (create takes space_id)GET|PATCH|DELETE /workspaces/:ws/databases/:dbGET|POST /.../databases/:db/fields PATCH|DELETE /fields/:fieldPOST /.../fields/:field/options PATCH|DELETE per optionPOST /workspaces/:ws/relations DELETE /relations/:relGET|POST /.../databases/:db/records (POST supports batch ≤100)GET|PATCH|DELETE /.../records/:recPOST /.../databases/:db/records/query ← the workhorsePOST /.../records/:rec/move { before_record_id? | after_record_id?, values? }GET|PUT /.../records/:rec/links/:field (list/replace) · POST add · DELETE removeGET|PUT /.../records/:rec/document (PUT requires expected version → 409)GET|POST /.../records/:rec/comments PATCH|DELETE /comments/:idGET|POST /.../records/:rec/attachments DELETE /attachments/:idGET /.../records/:rec/activityGET|POST /.../databases/:db/views PATCH|DELETE /views/:viewPOST /workspaces/:ws/templates/:slug/applyGET|POST /me/tokens DELETE /me/tokens/:idThe complete, always-current list with schemas is the API Reference.
The query endpoint
Section titled “The query endpoint”Filter trees don’t fit in GET params, so POST /records/query carries them. GET /records
remains for the simple case (?limit&cursor&q= title search, default order).
{ "filter": { "and": [ { "field": "f-uuid", "op": "eq", "value": "opt-uuid" }, { "or": [ { "field": "f2", "op": "gt", "value": 5 }, { "field": "f3", "op": "is_empty" } ] } ] }, "sorts": [ { "field": "f2", "direction": "desc" } ], "q": "acme", "expand": ["project"], "limit": 50, "cursor": "opaque..."}Limits: nesting depth ≤ 3, ≤ 50 conditions, limit ≤ 200, expand one level.
Operator × type matrix
Section titled “Operator × type matrix”| Op | text/url/email | number | date | checkbox | select | multi_select | user | relation |
|---|---|---|---|---|---|---|---|---|
eq / neq |
✅ | ✅ | ✅ | ✅ | ✅ | — | ✅ | — |
contains |
✅ | — | — | — | — | — | — | — |
gt gte lt lte |
— | ✅ | — | — | — | — | — | — |
before / after / within |
— | — | ✅ | — | — | — | — | — |
has / has_none |
— | — | — | — | ✅ | ✅ | ✅ | ✅ (record ids) |
is_empty / not_empty |
✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | ✅ |
within accepts relative ranges (today, next_7_days, this_month, …). User filters accept the
literal "me", resolved server-side. An invalid op-for-type returns 422.
Pagination
Section titled “Pagination”Keyset cursors only — an opaque base64url of {sort_values, id}. Responses are
{ "data": [...], "next_cursor": "..." | null, "has_more": true }. No offset pagination anywhere.
Errors — one envelope everywhere
Section titled “Errors — one envelope everywhere”{ "error": { "code": "validation_failed", "message": "...", "details": [{ "path": "values.f-uuid", "message": "expected number" }], "request_id": "req_..." } }Stable codes: unauthorized, forbidden, not_found, conflict, validation_failed,
rate_limited. Optimistic-concurrency conflicts return 409 with the current version in details.
Guest access to unshared spaces returns not_found (404), never 403 — the API doesn’t leak
existence.
Record payloads
Section titled “Record payloads”Values are keyed by stable api_name in requests and responses (field UUIDs are internal).
Relation fields return {id, title} chips. System fields are read-only.
Rate limiting
Section titled “Rate limiting”Keyed per PAT / per session. Default 300 req/min, configurable via RATE_LIMIT_PER_MINUTE. Over
the limit returns 429 with Retry-After.