Sortd Docs

Create a task — plain, or from an email

Tasks live in a list, on a board, in a team. This walks that hierarchy and creates a task two ways: a plain task, and a task created from an email thread — the API equivalent of dragging the email onto a board.

A plain task needs a key with tasks.write (plus boards.read / lists.read to discover ids). Creating from an email additionally needs email.metadata.read — the thread is read to build the card. See Authenticate first.

1. Find your team

curl "https://api.sortd.com/v2/teams" \
  -H "Authorization: Bearer sk_live_…"
# -> { "data": [ { "id": "<team_id>", "name": "Acme" } ], "meta": { … } }

2. Pick a board

curl "https://api.sortd.com/v2/boards?team_id=<team_id>" \
  -H "Authorization: Bearer sk_live_…"
# -> { "data": [ { "id": "brd_…", "name": "Support", "board_type": "shared" } ], … }

3. Pick a list on that board

curl "https://api.sortd.com/v2/boards/brd_…/lists" \
  -H "Authorization: Bearer sk_live_…"
# -> { "data": [ { "id": "lst_…", "name": "To do", "board_id": "brd_…" } ], … }

4a. Create a plain task

Send list_id and title (required), plus optional notes and due_at. Include an Idempotency-Key so a retried request can't create a duplicate.

curl

curl -X POST "https://api.sortd.com/v2/tasks" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: <unique-key>" \
  -d '{
    "list_id": "lst_…",
    "title": "Follow up with Acme re: renewal",
    "notes": "Sent pricing on Tuesday",
    "due_at": "2026-06-15T09:00:00Z"
  }'

JavaScript (fetch)

const res = await fetch("https://api.sortd.com/v2/tasks", {
  method: "POST",
  headers: {
    Authorization: "Bearer sk_live_…",
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    list_id: "lst_…",
    title: "Follow up with Acme re: renewal",
    notes: "Sent pricing on Tuesday",
  }),
});
const { data } = await res.json();

C# (HttpClient)

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", "sk_live_…");
client.DefaultRequestHeaders.Add("Idempotency-Key", Guid.NewGuid().ToString());
var content = new StringContent("""
{ "list_id": "lst_…", "title": "Follow up with Acme re: renewal" }
""", Encoding.UTF8, "application/json");
var res = await client.PostAsync("https://api.sortd.com/v2/tasks", content);
var json = await res.Content.ReadAsStringAsync();

Python (requests)

import requests, uuid
res = requests.post(
    "https://api.sortd.com/v2/tasks",
    headers={
        "Authorization": "Bearer sk_live_…",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"list_id": "lst_…", "title": "Follow up with Acme re: renewal"},
)
task = res.json()["data"]

4b. Create a task from an email

Send thread_id — the raw Gmail thread id (hex) — instead of composing the card yourself. The task's title defaults to the email subject (send title only to override it), the thread stays linked to the task so replies keep landing on it, and notes / due_at work exactly as in 4a. Optional archive: true also archives the email in Gmail once it's on the board — mirroring the drag gesture.

Needs tasks.write and email.metadata.read. Get thread ids from GET /email-threads?q=… (a raw Gmail search) or from webhook payloads.

curl — title comes from the email subject

curl -X POST "https://api.sortd.com/v2/tasks" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: <unique-key>" \
  -d '{
    "list_id": "lst_…",
    "thread_id": "18c2a5b7f3d94e01",
    "due_at": "2026-06-15T09:00:00Z",
    "archive": true
  }'

JavaScript — find the thread, then task it

const search = await fetch(
  "https://api.sortd.com/v2/email-threads?q=" + encodeURIComponent("from:jane@acme.com renewal"),
  { headers: { Authorization: "Bearer sk_live_…" } }
);
const thread = (await search.json()).data[0];

const res = await fetch("https://api.sortd.com/v2/tasks", {
  method: "POST",
  headers: {
    Authorization: "Bearer sk_live_…",
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({ list_id: "lst_…", thread_id: thread.thread_id }),
});
const { data } = await res.json(); // data.title === the email subject

C# (HttpClient)

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", "sk_live_…");
client.DefaultRequestHeaders.Add("Idempotency-Key", Guid.NewGuid().ToString());
var content = new StringContent("""
{ "list_id": "lst_…", "thread_id": "18c2a5b7f3d94e01", "archive": true }
""", Encoding.UTF8, "application/json");
var res = await client.PostAsync("https://api.sortd.com/v2/tasks", content);

Python (requests)

import requests, uuid
res = requests.post(
    "https://api.sortd.com/v2/tasks",
    headers={
        "Authorization": "Bearer sk_live_…",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"list_id": "lst_…", "thread_id": "18c2a5b7f3d94e01"},
)
task = res.json()["data"]  # task["title"] == the email subject

5. Choose where the card lands

By default a new task goes to the top of the list — the same as an automation-created card. Both forms of create accept:

FieldEffect
"position": "top"Top of the list (the default — you can omit it).
"position": "bottom"Bottom of the list. Use this when creating several tasks in sequence — with the default, each new card lands above the last and the batch comes out in reverse order.
"after_task_id": "tsk_…"Directly below that task. Takes precedence over position.
# curl — a batch that keeps its order
curl -X POST "https://api.sortd.com/v2/tasks" \
  -H "Authorization: Bearer sk_live_…" -H "Content-Type: application/json" \
  -d '{ "list_id": "lst_…", "title": "Step 1", "position": "bottom" }'
curl -X POST "https://api.sortd.com/v2/tasks" \
  -H "Authorization: Bearer sk_live_…" -H "Content-Type: application/json" \
  -d '{ "list_id": "lst_…", "title": "Step 2", "position": "bottom" }'

# curl — slot a task directly below another
curl -X POST "https://api.sortd.com/v2/tasks" \
  -H "Authorization: Bearer sk_live_…" -H "Content-Type: application/json" \
  -d '{ "list_id": "lst_…", "title": "Chase invoice", "after_task_id": "tsk_…" }'

Repositioning tasks that already exist — including a full AI re-prioritisation — is its own guide: Order & reorder tasks.

6. Link more emails to a task

A task can carry several threads. POST /tasks/{id}/threads links another one (needs tasks.write + email.metadata.read). Linking a thread that's already on the task is a no-op, so retries are safe. The response returns the task's full thread_ids.

curl -X POST "https://api.sortd.com/v2/tasks/tsk_…/threads" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "thread_id": "18c2b90a44e7d2af", "archive": true }'
# -> { "data": { "id": "tsk_…", "thread_ids": ["18c2a5b7f3d94e01", "18c2b90a44e7d2af"] } }

7. The created task

{
  "data": {
    "id": "tsk_…",
    "title": "Q3 renewal",
    "list_id": "lst_…",
    "completed": false,
    "due_at": "2026-06-15T09:00:00Z",
    "created_at": "2026-06-09T12:00:00Z",
    "thread_ids": ["18c2a5b7f3d94e01"]
  },
  "meta": { "request_id": "req_…" }
}

thread_ids holds every linked thread — pass them to the email endpoints (GET /email-threads/{id}, reply, drafts). Mark the task done with POST /tasks/{id}/complete, or move it with POST /tasks/{id}/move.

8. Edit the task

PATCH /tasks/{id} is a partial update — send only what changes. It supports the task colour (the shade field) as a named value; "none" clears it.

shade: red · orange · deep_orange · amber · yellow · yellow_green · lime · green · teal · cyan · light_blue · blue · indigo · purple · magenta · pink · brown · grey · dark_grey · black · none

curl — set the task colour to red

curl -X PATCH "https://api.sortd.com/v2/tasks/tsk_…" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "shade": "red" }'

JavaScript — clear the colour

await fetch("https://api.sortd.com/v2/tasks/tsk_…", {
  method: "PATCH",
  headers: { Authorization: "Bearer sk_live_…", "Content-Type": "application/json" },
  body: JSON.stringify({ shade: "none" }),
});

Python — set the colour to lime

import requests
requests.patch(
    "https://api.sortd.com/v2/tasks/tsk_…",
    headers={"Authorization": "Bearer sk_live_…"},
    json={"shade": "lime"},
)

The response is the full updated task, with shade as a colour name.

9. Set tags, status & other custom fields

Tags and status are custom fields, set through the same PATCH. First read the board's fields to get each field_id and, for tag/status/select fields, the option ids:

curl "https://api.sortd.com/v2/boards/brd_…/custom-fields" \
  -H "Authorization: Bearer sk_live_…"
# -> { "data": [
#      { "id": "cf_status", "name": "Status", "type": "status",
#        "options": [ { "id": "opt_open", "label": "Open" }, { "id": "opt_done", "label": "Done" } ] },
#      { "id": "cf_priority", "name": "Priority", "type": "tag",
#        "options": [ { "id": "opt_hi", "label": "High" }, { "id": "opt_lo", "label": "Low" } ] }
#    ], … }

Then set values by field_id. For tag/status/select, value is an array of option ids ([] clears it); for text/number/date it's the raw value. Needs the customfields.write scope.

Fields flagged "ai": true (managed by Sortd AI, e.g. Response Status) are read-only — setting them returns a 400.

curl — set Status = Open and Priority = High

curl -X PATCH "https://api.sortd.com/v2/tasks/tsk_…" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "custom_fields": [
      { "field_id": "cf_status", "value": ["opt_open"] },
      { "field_id": "cf_priority", "value": ["opt_hi"] }
    ]
  }'

JavaScript — set a text custom field

await fetch("https://api.sortd.com/v2/tasks/tsk_…", {
  method: "PATCH",
  headers: { Authorization: "Bearer sk_live_…", "Content-Type": "application/json" },
  body: JSON.stringify({ custom_fields: [{ field_id: "cf_ref", value: "ACME-1234" }] }),
});

Python — change a colour and a custom field in one call

import requests
requests.patch(
    "https://api.sortd.com/v2/tasks/tsk_…",
    headers={"Authorization": "Bearer sk_live_…"},
    json={
        "shade": "red",
        "custom_fields": [{"field_id": "cf_status", "value": ["opt_done"]}],
    },
)

The updated task's custom_fields holds the stored values — option ids for tag/status; resolve their labels via the board's custom-fields endpoint above.

Next: order & reorder tasks, or classify a thread with AI.