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:
| Field | Effect |
|---|---|
"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. Each value is typed by the property's type, the same shape you get back when you read the task; null clears any of them. Needs the customfields.write scope. If any value in the request is invalid, nothing is changed.
| Type | Write | Read back |
|---|---|---|
text, url, phone, email, location | a string (URLs must be http(s); email is checked) | the string |
multiline_text | a string with line breaks | the same string |
number | a number (or a numeric string) | a number |
date | YYYY-MM-DD (shown as that day in every time zone) or an ISO date-time | an ISO date-time |
checkbox | true / false | true / false, plus checked_at |
select, status | one option id or label | { "id", "label" } or null |
multiselect, tag | a list of option ids or labels | a list of { "id", "label" } |
person | the email of a member of the task's board | { "email", "name" } |
contact / company | a con_… / cmp_… id from the task's organisation | { "id", "name" } |
attachment | the full list: { "id" } keeps one, { "url", "name" } adds a link | a list of { "id", "name", "url", "mime_type", "provider" } |
tasks (sub-tasks) | use the sub-task endpoints below | a list of { "id", "title", "completed", "completed_at", "assignee", "position" } |
Fields flagged "ai": true (managed by Sortd AI, e.g. Response Status) take values the same way, and behave as in the app: setting Response Status to Actioned clears the AI fields rather than storing Actioned. Their settings (name, options, display) cannot be changed and they cannot be deleted — those requests return 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": ["High"] }
]
}'
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": "Done"}],
},
)
The updated task's custom_fields lists every property with its type, name and typed value, for example:
"custom_fields": [
{ "field_id": "cf_status", "type": "status", "name": "Status", "value": { "id": "opt_done", "label": "Done" } },
{ "field_id": "cf_owner", "type": "person", "name": "Owner", "value": { "email": "sam@acme.com", "name": "Sam" } },
{ "field_id": "cf_ok", "type": "checkbox", "name": "Approved", "value": true, "checked_at": "2026-10-02T09:14:00.000Z" }
]
Sub-tasks
A Task list property (type tasks) holds sub-tasks. Change them one at a time — add, then tick, rename, assign (a board member's email) or move (position), or delete. Each change is recorded in the task history as in the app. Needs tasks.write and customfields.write.
# add
curl -X POST "https://api.sortd.com/v2/tasks/tsk_…/custom-fields/cf_checklist/subtasks" \
-H "Authorization: Bearer sk_live_…" -H "Content-Type: application/json" \
-d '{ "title": "Send the signed contract", "assignee": "sam@acme.com" }'
# tick it (subtask id from the response or the task's custom_fields)
curl -X PATCH "https://api.sortd.com/v2/tasks/tsk_…/custom-fields/cf_checklist/subtasks/<subtask_id>" \
-H "Authorization: Bearer sk_live_…" -H "Content-Type: application/json" \
-d '{ "completed": true }'
Next: order & reorder tasks, or classify a thread with AI.