Help

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 /tasks with { "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/restore brings 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; detail says 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; detail says how.
  • 412 - it was saved since your If-Match.
  • 422 - an Idempotency-Key used for a different request.
  • 429 - too many requests (a demo account's limits); wait for Retry-After seconds.

Search help

Find a help topic

The REST API - Plan Things Better help