The REST API
The REST API lets your own code - a script, an integration, a report - read and change your workspaces over HTTP. It uses the same API keys as AI tools, with the same switches, and it sees what you see and acts as you.
Its address is /api/v1 on this site. You'll find it, and a ready example,
in Settings → Account → API keys, under Your code (REST API). If
that isn't there, the REST API isn't available right now.
#Signing in
Send your key with every request:
curl https://planthingsbetter.com/api/v1/workspaces \
-H "Authorization: Bearer $PTB_KEY"
That lists the workspaces the key can reach, each with its slug.
Everything else is under a workspace: /api/v1/workspaces/<slug>/....
Only the key signs in. Being signed in to the app in your browser doesn't, so a web page can't use the API as you.
#What you can do
Things go by their keys - T-0042, P-0003, R-0030 - never by name, and
never by a database id. The verb says what happens:
- GET reads:
/tasks,/tasks/T-0042,/projects/P-0003/tasks. - POST makes something:
POST /taskswith{ "title": "Fix the login" }. - PATCH changes only the fields you send:
{ "status": "Done" }. - DELETE moves something to the Trash.
Nothing is deleted for good, and
POST /trash/restorebrings it back.
There's a resource for everything you can see in the app: tasks (and their
comments and checklists), projects, milestones, labels, saved views, dashboards,
docs (and a table doc's rows), templates, custom fields, Spaces, chat
channels and messages, folders, brands, the workspace's statuses, priorities
and types, members, Activity, your
Inbox, favorites, the Trash and search. The full
list, with every field, is the reference at /api/v1/openapi.json -
import it into Postman, Insomnia or a client generator.
GET /tasks?q= takes the query language,
the same as the query bar: ?q=assignee:me is:open sort:due.
GET /views/V-0002/tasks runs a saved view - one kept in a Space lists only
that Space's tasks, as the app does. Each view and dashboard says its space,
when anyone last opened it (openedAt) and whether it's stale (nobody has in
60 days); GET /views?stale=true and GET /dashboards?stale=true list just
those. A project, milestone, label or member comes with shownIn: the views and
dashboards whose query names it.
To move projects to another Space, POST /spaces/move with
{ "space": "Acme", "projects": ["P-0003", "P-0004"] } moves them together,
so tasks linked between them don't hold any back ("space": null is the whole
workspace). Add ?dryRun=true to see what would move, or why it can't, first.
PATCH /folders/projects?path=Clients with { "space": "Acme" } moves
everything in a folder the same way. See
Moving a project into a Space.
#What a key may do
A key can always read. Its switches in Settings → Account → API keys decide the rest:
- Let it make changes - POST, PATCH and PUT.
- Let it move things to the Trash - DELETE, and restoring.
With a switch off, the request is refused, saying which switch it needs. Everything a key changes shows in Activity as you, "via" the key's name.
#Pages
A list comes a page at a time: 50 by default, up to 200 with ?limit=.
When there's more, the reply's nextCursor gets the next page - send it as
?cursor=. On the last page it's null.
#Not overwriting someone's change
Every item comes with an ETag: the version you read. Send it back as
If-Match with a PATCH or DELETE, and the change is refused with
412 Precondition Failed if someone saved it since - read it again and
decide.
#Sending again safely
If a POST times out, you can't tell whether it worked. Send it with an
Idempotency-Key header (any text, such as a UUID) and send it again with
the same key: you get the first answer, and nothing is made twice. Keys are
kept a day. The same key on a different request is refused.
#When something's wrong
Errors come back as application/problem+json: the HTTP status, a detail
saying what to do, and a code your code can check.
- 400 - a field or parameter isn't right;
detailsays which. - 401 - no key, or the key isn't valid (mistyped, or revoked).
- 403 - a switch on the key is off, or the app doesn't allow it.
- 404 - there's no such thing, or it's not one you can see.
- 405 - that can't be done through the API yet;
detailsays how. - 412 - it was saved since your
If-Match. - 422 - an
Idempotency-Keyused for a different request. - 429 - too many requests (a demo account's limits); wait for
Retry-Afterseconds.