Skip to content

Conventions

All request and response fields use snake_case — e.g. due_date, task_status_id.

Successful responses are wrapped in a data envelope:

{ "data": { "id": "task_6a2918cff58b0a1d387866ee" } }

List endpoints add a pagination object:

{
"data": [ /* … */ ],
"pagination": { "next_cursor": "", "has_more": true }
}

Errors use a separate envelope — see Errors.

List endpoints are cursor-paginated. Pass the next_cursor from the previous response to fetch the next page; has_more tells you when to stop. Treat the cursor as opaque.

GET /public/v1/tasks accepts filters as query parameters: priority, task_status_id, state (open / closed), task_type_id, assignee_id, due_date_from / due_date_to / due_date, tag_id. Without filters it returns every task in the scope — the same set you see in the project’s table view in the web app.

Terminal window
curl "https://api.bordio.com/public/v1/tasks?project_id=proj_6594a1b2c3d4e5f6a7b8c9d1&state=open&priority=high,critical" \
-H "Authorization: Bearer brd_sk_live_..."

How filters combine:

  • Different parameters → AND. state=open&priority=high returns open tasks that are also high-priority.
  • Multiple values within one parameter → OR. Values are comma-separated: priority=high,critical matches either priority. tag_id=tag_a,tag_b matches tasks that have any of the listed tags.
  • Ids use their public form (task_status_…, task_type_…, user_…, tag_…). A malformed value returns 400 validation_error; a well-formed id that matches nothing simply yields an empty page. The exception is project_id: a project outside your key’s scope returns 404 not_found_error.
  • priority also accepts none — tasks with no priority set (priority=none,low = no priority or low).
  • assignee_id accepts the special value none — tasks with no assignee. It can be mixed with user ids: assignee_id=user_6594a1b2c3d4e5f6a7b8c9d1,none (either that user, or nobody).
  • due_date_from / due_date_to accept YYYY-MM-DD (interpreted as the start / end of that day, UTC) or a full ISO-8601 datetime. Tasks without a due date never match a range.
  • due_date=none returns only tasks without a due date. It cannot be combined with due_date_from / due_date_to (400 validation_error).

Recipes for common queries (unassigned tasks, overdue view, tasks with no priority) live in Filtering tasks. The full parameter list with schemas lives in the API Reference.

PATCH endpoints distinguish two things: an omitted field is left unchanged, and a field sent as null clears its value — but only where “no value” makes sense:

Endpoint Fields that accept null Effect
PATCH /tasks/:id priority, due_date, description, assignee_ids, reporter_ids, tag_ids Clears the value (assignee_ids: null = nobody; tag_ids/reporter_ids: same as [])
PATCH /tasks/:id/subtasks/:id assignee_id, due_date Unassigns / removes the due date
PATCH /scheduled-events/:id description, location, reminder_minutes_before Clears the text / removes the reminder

Everything else is non-clearable: sending null to a required field (title, task_status_id, start_at, …) returns 400 validation_error instead of being silently ignored. To reset a numeric estimate, send 0; to clear a single custom field value, send its custom_fields item with a null value.

Note the difference from filters: in GET /tasks query parameters, JSON null doesn’t exist, so the special string value none plays the same role (assignee_id=none, priority=none, due_date=none) — see Filtering tasks.

POST requests (creates) accept an Idempotency-Key header. Send a unique key (up to 255 characters — a UUID works well) so a retried request can’t create a duplicate:

Terminal window
curl -X POST https://api.bordio.com/public/v1/tasks \
-H "Authorization: Bearer brd_sk_live_..." \
-H "Idempotency-Key: 6a2918cf-f58b-0a1d-3878-66ee00000001" \
-H "Content-Type: application/json" \
-d '{ "title": "New task" }'

How it behaves:

  • Same key, same body → the original result is replayed; nothing new is created.
  • Same key, different body409 conflict_error (already_exists).
  • Same key while the first request is still in flight409 conflict_error — retry once the first one finishes.
  • Keys are remembered for 24 hours, then expire — reusing a key after that is treated as a brand-new request.
  • A key is scoped to its endpoint (and workspace): the same key on a different endpoint is independent.
  • Only POST is idempotent via this header. PATCH/DELETE are not — a PATCH is naturally safe to repeat, since it sets fields to the values you send.