HTTP API
One door for everything outside the app: a script, a bot, n8n, somebody's spreadsheet.
It is deliberately small. Not a mirror of the app — records in and records out.
Authentication
Make a key in Settings → API and webhooks. It is shown once and stored hashed.
curl -H "authorization: Bearer tuv_..." \
https://<project>.supabase.co/functions/v1/api/records?kind=issue
The key decides which workspace the call reads and writes. The caller never chooses it, and there is no parameter that can move a call into another workspace.
Available on the team plan, and on any install where
self_hosted is true. On a free hosted
workspace every call answers 401.
Records
A record is one row of work: an issue, a page, a database, a project. kind says which.
issue · doc · database · project · person · company · event · file
GET /records
| Query | Meaning |
|---|---|
kind |
One of the kinds above |
status |
backlog todo doing review blocked done cancelled |
assignee |
A user id |
project · cycle |
A record id |
limit · offset |
Page through. Archived rows are left out |
GET /records/<id>
One record.
GET /records/<id>/markdown
The same record with its body rendered as Markdown — headings, lists and code intact. This is the endpoint to read a page with, not the JSON one.
POST /records
curl -X POST -H "authorization: Bearer tuv_..." -H "content-type: application/json" \
-d '{"title":"Ship the API reference","status":"todo","priority":2}' \
https://<project>.supabase.co/functions/v1/api/records
kind defaults to issue. Returns 201 and the created row.
PATCH /records/<id>
Send only what changes.
curl -X PATCH -H "authorization: Bearer tuv_..." -H "content-type: application/json" \
-d '{"status":"done"}' \
https://<project>.supabase.co/functions/v1/api/records/<id>
DELETE /records/<id>
Archives rather than deletes — an integration having a bad day cannot take work away for good.
Returns {"archived":"<id>"}.
Writable fields
kind title description status assignee priority due_at estimate parent_id
project_id cycle_id position data
Anything else you send is dropped rather than refused, so a client that grew a field this version does not have keeps working.
data is a free JSON object for fields that are yours, not ours.
Everything else
GET /search?q=<words>&limit= |
Titles and bodies, across every kind. Returns an excerpt and an id |
GET /cycles |
The two-week cycles |
GET /labels |
The workspace's labels |
GET / |
What this key can reach |
Errors
401 no key, or a key that is not valid, or the API is off for this plan · 400 malformed body
or a rejected write · 404 no such record in this workspace · 405 a method this door does not do.
Related
- MCP server — the same data, mounted in an agent
- Self-hosting