Capri API
Your tasks, from anywhere
With an API token, scripts, shortcuts and other apps can read and change your Capri tasks, projects, labels, comments, deadlines and schedule. 27 endpoints, all JSON, the same for accounts kept by Capri and accounts synced with Todoist.
- Make a token. In Capri, open Account → API tokens → New token, name it and copy it. It starts with
capri_and is only shown once. - Send it with each request in the
Authorizationheader. - Try it: this gets everything open.
curl https://capritasks.xyz/api/bootstrap \
-H "Authorization: Bearer capri_YOUR_TOKEN"const res = await fetch('https://capritasks.xyz/api/bootstrap', {
headers: { Authorization: 'Bearer capri_YOUR_TOKEN' },
});
const data = await res.json();import requests
res = requests.get(
"https://capritasks.xyz/api/bootstrap",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->get("/api/bootstrap");
std::cout << res.status << " " << res.body << "\n";
}And this adds a task:
curl -X POST https://capritasks.xyz/api/tasks \
-H "Authorization: Bearer capri_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"content": "Send the report", "due_string": "tomorrow 5pm", "priority": 4, "labels": ["errands"]}'const res = await fetch('https://capritasks.xyz/api/tasks', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
"content": "Send the report",
"due_string": "tomorrow 5pm",
"priority": 4,
"labels": [
"errands"
]
}),
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/tasks",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
json={
"content": "Send the report",
"due_string": "tomorrow 5pm",
"priority": 4,
"labels": [
"errands"
]
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/tasks",
netcpp::Body::json(R"({"content": "Send the report", "due_string": "tomorrow 5pm", "priority": 4, "labels": ["errands"]})"));
std::cout << res.status << " " << res.body << "\n";
}Tokens and safety
- A token acts as you, so keep it secret like a password. Don’t put it in a public repository or a web page.
- You can have up to 10. Name each after what uses it, so you know which to delete.
- Delete one in Account → API tokens and anything using it stops straight away. A password reset or recovery-code sign-in deletes them all. One unused for 180 days ends by itself.
- Signing out everywhere or changing your password keeps them.
- A token can’t sign in, change your password, email, passkeys or plan, use Co-Work, or make more tokens. Those answer
403.
Permissions
When you make a token you give it Full access, or only the permissions you tick. You can change them later from Account → API tokens → Permissions, and the change applies straight away. Each endpoint below shows the permission it needs. Without it, the reply is 403 with "scope" naming the missing one.
| Permission | Lets the token |
|---|---|
admin | Full access: everything below, and anything added later. |
read | Read tasks, projects, sections, labels, comments, completed tasks, deadlines, the schedule and your account’s basics. |
tasks | Add, change, move, complete, reopen and delete tasks. |
comments | Add comments. |
projects | Add, change, reorder and delete projects, sections and labels. |
deadlines | Set and clear deadlines. |
schedule | Change the time-block schedule (needs Capri+). |
Give each token only what it needs. A script that just shows today’s tasks only needs read.
C++ with netc++
The C++ examples use netc++, a C++17 network library for Windows, macOS and Linux where everything goes through ->. Add it to a CMake project:
include(FetchContent)
FetchContent_Declare(netcpp
GIT_REPOSITORY https://github.com/Unblonded/netcpp.git
GIT_TAG v1.0.0)
FetchContent_MakeAvailable(netcpp)
target_link_libraries(your_app PRIVATE netcpp::netcpp)Then make one client, give it your token, and call any endpoint. Replies have status, body and ok(); network failures throw netcpp::Error.
#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
using namespace std::chrono_literals;
auto capri = netcpp::api("https://capritasks.xyz/api");
capri->bearer("capri_YOUR_TOKEN")
->header("X-Capri-TZ", "Europe/London")
->timeout(10s);
// Today's tasks
auto today = capri->get("/filter", {{"query", "today"}});
std::cout << today.body << "\n";
// Add one
auto task = capri->post("/tasks", netcpp::Body::json(
R"({"content": "Buy milk", "due_string": "tomorrow 5pm"})"));
if (!task.ok()) std::cerr << task.status << " " << task.body << "\n";
}Requests and replies
- The address is
https://capritasks.xyz. Every endpoint starts with/api/. - Send bodies as JSON with
Content-Type: application/json. Replies are JSON too. - Each token can make 120 requests a minute. After that it gets
429until the minute is up. - If your tasks are kept by Capri, add your time zone so words like “today” and “tomorrow 5pm” mean your day:
X-Capri-TZ: Europe/London. - Ids are strings. Get them from Everything open, or from the reply when you add something.
Reading
Everything open
All your projects, sections, labels and open tasks in one go. This is what Capri loads when it starts, so it’s the easiest way to get a full picture.
curl https://capritasks.xyz/api/bootstrap \
-H "Authorization: Bearer capri_YOUR_TOKEN"const res = await fetch('https://capritasks.xyz/api/bootstrap', {
headers: { Authorization: 'Bearer capri_YOUR_TOKEN' },
});
const data = await res.json();import requests
res = requests.get(
"https://capritasks.xyz/api/bootstrap",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->get("/api/bootstrap");
std::cout << res.status << " " << res.body << "\n";
}{
"projects": [
{
"id": "ciy0N4oeU6DMn",
"name": "Work",
"color": "blue",
"parent_id": null,
"child_order": 1,
"inbox_project": false,
"is_archived": false,
"is_favorite": false,
"view_style": "list"
}
],
"sections": [
{
"id": "cKYAq921AUF6H",
"project_id": "ciy0N4oeU6DMn",
"name": "This week",
"section_order": 1,
"is_archived": false
}
],
"labels": [
{
"id": "cxrxS24xnNyoY",
"name": "errands",
"color": "green",
"item_order": 1,
"is_favorite": false
}
],
"tasks": [
{
"id": "cp1u6O9InfekT",
"content": "Send the report",
"description": "The Q4 numbers",
"project_id": "ciy0N4oeU6DMn",
"section_id": "cKYAq921AUF6H",
"parent_id": null,
"labels": [
"errands"
],
"priority": 4,
"due": {
"date": "2026-11-10T17:00:00",
"string": "tomorrow 5pm",
"is_recurring": false,
"timezone": null,
"lang": "en"
},
"checked": false,
"completed_at": null,
"added_at": "2026-11-09T20:27:12.688Z",
"updated_at": "2026-11-09T20:27:12.716Z",
"child_order": 3,
"day_order": -1,
"note_count": 0,
"is_collapsed": false
}
]
}Filter tasks
Open tasks matching a query. On an account synced with Todoist this uses Todoist’s filters (today, overdue, #Work & p1). On an account whose tasks are kept by Capri, it finds tasks whose name contains the words.
Address (query)
| Field | Type | What it is |
|---|---|---|
queryrequired | string | The filter or words to look for. Goes in the address. |
curl https://capritasks.xyz/api/filter?query=today \
-H "Authorization: Bearer capri_YOUR_TOKEN"const res = await fetch('https://capritasks.xyz/api/filter?query=today', {
headers: { Authorization: 'Bearer capri_YOUR_TOKEN' },
});
const data = await res.json();import requests
res = requests.get(
"https://capritasks.xyz/api/filter",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
params={
"query": "today"
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->get("/api/filter", {{"query", "today"}});
std::cout << res.status << " " << res.body << "\n";
}{
"tasks": [
{
"id": "cp1u6O9InfekT",
"content": "Send the report",
"description": "The Q4 numbers",
"project_id": "ciy0N4oeU6DMn",
"section_id": "cKYAq921AUF6H",
"parent_id": null,
"labels": [
"errands"
],
"priority": 4,
"due": {
"date": "2026-11-10T17:00:00",
"string": "tomorrow 5pm",
"is_recurring": false,
"timezone": null,
"lang": "en"
},
"checked": false,
"completed_at": null,
"added_at": "2026-11-09T20:27:12.688Z",
"updated_at": "2026-11-09T20:27:12.716Z",
"child_order": 3,
"day_order": -1,
"note_count": 0,
"is_collapsed": false
}
]
}Completed tasks
Tasks you’ve finished between two times. Free accounts see the last 7 days; Capri+ can go back further.
Address (query)
| Field | Type | What it is |
|---|---|---|
since | string | ISO time to start from. Defaults to a week ago. |
until | string | ISO time to end at. Defaults to now. |
Accounts synced with Todoist may also get "partial": true when Todoist only sent part of the history.
curl https://capritasks.xyz/api/completed?since=2026-11-01T00:00:00Z \
-H "Authorization: Bearer capri_YOUR_TOKEN"const res = await fetch('https://capritasks.xyz/api/completed?since=2026-11-01T00:00:00Z', {
headers: { Authorization: 'Bearer capri_YOUR_TOKEN' },
});
const data = await res.json();import requests
res = requests.get(
"https://capritasks.xyz/api/completed",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
params={
"since": "2026-11-01T00:00:00Z"
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->get("/api/completed", {{"since", "2026-11-01T00:00:00Z"}});
std::cout << res.status << " " << res.body << "\n";
}{
"items": [
{
"id": "cp1u6O9InfekT",
"content": "Send the report",
"description": "The Q4 numbers",
"project_id": "ciy0N4oeU6DMn",
"section_id": "cKYAq921AUF6H",
"parent_id": null,
"labels": [
"errands"
],
"priority": 4,
"due": {
"date": "2026-11-10T17:00:00",
"string": "tomorrow 5pm",
"is_recurring": false,
"timezone": null,
"lang": "en"
},
"checked": true,
"completed_at": "2026-11-10T16:42:03.120Z",
"added_at": "2026-11-09T20:27:12.688Z",
"updated_at": "2026-11-09T20:27:12.716Z",
"child_order": 3,
"day_order": -1,
"note_count": 0,
"is_collapsed": false
}
]
}Your account
Who the token belongs to: username, email, plan and whether tasks are kept by Capri (mode: "capri") or in Todoist ("both" or "todoist").
The reply has a few more fields the app uses; the ones above are the useful ones.
curl https://capritasks.xyz/api/status \
-H "Authorization: Bearer capri_YOUR_TOKEN"const res = await fetch('https://capritasks.xyz/api/status', {
headers: { Authorization: 'Bearer capri_YOUR_TOKEN' },
});
const data = await res.json();import requests
res = requests.get(
"https://capritasks.xyz/api/status",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->get("/api/status");
std::cout << res.status << " " << res.body << "\n";
}{
"connected": true,
"account": {
"mode": "capri",
"username": "sam",
"email": "[email protected]",
"plan": "free",
"until": null
}
}Tasks
Add a task
Adds a task and replies with it, including its new id.
Body (JSON)
| Field | Type | What it is |
|---|---|---|
contentrequired | string | The task’s name. Markdown links and **bold** work. |
description | string | Notes under the name. |
due_string | string | A date in words: tomorrow 5pm, next friday, every monday, every 2 weeks. |
due_date | string | A day, YYYY-MM-DD. null clears the date. |
due_datetime | string | An exact time, ISO 8601 (2026-11-10T17:00:00Z). |
priority | integer | 4 is the most urgent (P1 in the app), 1 is normal (P4). |
project_id | string | Which project. Leave it out for the Inbox. |
section_id | string | Which section of that project. |
parent_id | string | Makes it a sub-task of this task. |
labels | string[] | Label names, like ["errands"]. |
curl -X POST https://capritasks.xyz/api/tasks \
-H "Authorization: Bearer capri_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"content": "Send the report", "due_string": "tomorrow 5pm", "priority": 4, "labels": ["errands"]}'const res = await fetch('https://capritasks.xyz/api/tasks', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
"content": "Send the report",
"due_string": "tomorrow 5pm",
"priority": 4,
"labels": [
"errands"
]
}),
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/tasks",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
json={
"content": "Send the report",
"due_string": "tomorrow 5pm",
"priority": 4,
"labels": [
"errands"
]
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/tasks",
netcpp::Body::json(R"({"content": "Send the report", "due_string": "tomorrow 5pm", "priority": 4, "labels": ["errands"]})"));
std::cout << res.status << " " << res.body << "\n";
}{
"id": "cp1u6O9InfekT",
"content": "Send the report",
"description": "The Q4 numbers",
"project_id": "ciy0N4oeU6DMn",
"section_id": "cKYAq921AUF6H",
"parent_id": null,
"labels": [
"errands"
],
"priority": 4,
"due": {
"date": "2026-11-10T17:00:00",
"string": "tomorrow 5pm",
"is_recurring": false,
"timezone": null,
"lang": "en"
},
"checked": false,
"completed_at": null,
"added_at": "2026-11-09T20:27:12.688Z",
"updated_at": "2026-11-09T20:27:12.716Z",
"child_order": 3,
"day_order": -1,
"note_count": 0,
"is_collapsed": false
}Quick add
Adds a task from one line, the way the app’s Add box does: #Project, @label and p1 to p4 are picked out of the text. On accounts synced with Todoist, dates in the text (tomorrow 10am) work too. On accounts kept by Capri, a repeat at the end (every monday) does; use due_string with Add a task for other dates.
Body (JSON)
| Field | Type | What it is |
|---|---|---|
textrequired | string | The whole line. |
curl -X POST https://capritasks.xyz/api/tasks/quick \
-H "Authorization: Bearer capri_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"text": "Call Sam tomorrow 10am #Work @errands p2"}'const res = await fetch('https://capritasks.xyz/api/tasks/quick', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
"text": "Call Sam tomorrow 10am #Work @errands p2"
}),
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/tasks/quick",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
json={
"text": "Call Sam tomorrow 10am #Work @errands p2"
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/tasks/quick",
netcpp::Body::json(R"({"text": "Call Sam tomorrow 10am #Work @errands p2"})"));
std::cout << res.status << " " << res.body << "\n";
}{
"id": "cIg5KxTmKOBXk",
"content": "Call Sam",
"description": "",
"project_id": "ciy0N4oeU6DMn",
"section_id": null,
"parent_id": null,
"labels": [
"errands"
],
"priority": 3,
"due": {
"date": "2026-11-10T10:00:00",
"string": "tomorrow 10am",
"is_recurring": false,
"timezone": null,
"lang": "en"
},
"checked": false,
"completed_at": null,
"added_at": "2026-11-09T20:27:12.688Z",
"updated_at": "2026-11-09T20:27:12.716Z",
"child_order": 3,
"day_order": -1,
"note_count": 0,
"is_collapsed": false
}Change a task
Changes only the fields you send. To move it to another project, section or parent, use Move a task.
Body (JSON)
| Field | Type | What it is |
|---|---|---|
contentrequired | string | The task’s name. Markdown links and **bold** work. |
description | string | Notes under the name. |
due_string | string | A date in words: tomorrow 5pm, next friday, every monday, every 2 weeks. |
due_date | string | A day, YYYY-MM-DD. null clears the date. |
due_datetime | string | An exact time, ISO 8601 (2026-11-10T17:00:00Z). |
priority | integer | 4 is the most urgent (P1 in the app), 1 is normal (P4). |
labels | string[] | Label names, like ["errands"]. |
curl -X POST https://capritasks.xyz/api/tasks/cp1u6O9InfekT \
-H "Authorization: Bearer capri_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"content": "Send the final report", "priority": 3}'const res = await fetch('https://capritasks.xyz/api/tasks/cp1u6O9InfekT', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
"content": "Send the final report",
"priority": 3
}),
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/tasks/cp1u6O9InfekT",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
json={
"content": "Send the final report",
"priority": 3
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/tasks/cp1u6O9InfekT",
netcpp::Body::json(R"({"content": "Send the final report", "priority": 3})"));
std::cout << res.status << " " << res.body << "\n";
}{
"id": "cp1u6O9InfekT",
"content": "Send the final report",
"description": "The Q4 numbers",
"project_id": "ciy0N4oeU6DMn",
"section_id": "cKYAq921AUF6H",
"parent_id": null,
"labels": [
"errands"
],
"priority": 3,
"due": {
"date": "2026-11-10T17:00:00",
"string": "tomorrow 5pm",
"is_recurring": false,
"timezone": null,
"lang": "en"
},
"checked": false,
"completed_at": null,
"added_at": "2026-11-09T20:27:12.688Z",
"updated_at": "2026-11-09T20:27:12.716Z",
"child_order": 3,
"day_order": -1,
"note_count": 0,
"is_collapsed": false
}Move a task
Moves a task, with its sub-tasks, somewhere else. Send one of the three.
Body (JSON)
| Field | Type | What it is |
|---|---|---|
project_id | string | To the top of this project. |
section_id | string | Into this section. |
parent_id | string | Under this task, as a sub-task. |
curl -X POST https://capritasks.xyz/api/tasks/cp1u6O9InfekT/move \
-H "Authorization: Bearer capri_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"section_id": "cKYAq921AUF6H"}'const res = await fetch('https://capritasks.xyz/api/tasks/cp1u6O9InfekT/move', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
"section_id": "cKYAq921AUF6H"
}),
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/tasks/cp1u6O9InfekT/move",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
json={
"section_id": "cKYAq921AUF6H"
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/tasks/cp1u6O9InfekT/move",
netcpp::Body::json(R"({"section_id": "cKYAq921AUF6H"})"));
std::cout << res.status << " " << res.body << "\n";
}{
"id": "cp1u6O9InfekT",
"content": "Send the report",
"description": "The Q4 numbers",
"project_id": "ciy0N4oeU6DMn",
"section_id": "cKYAq921AUF6H",
"parent_id": null,
"labels": [
"errands"
],
"priority": 4,
"due": {
"date": "2026-11-10T17:00:00",
"string": "tomorrow 5pm",
"is_recurring": false,
"timezone": null,
"lang": "en"
},
"checked": false,
"completed_at": null,
"added_at": "2026-11-09T20:27:12.688Z",
"updated_at": "2026-11-09T20:27:12.716Z",
"child_order": 3,
"day_order": -1,
"note_count": 0,
"is_collapsed": false
}Complete a task
Ticks it off. A repeating task moves on to its next date instead.
curl -X POST https://capritasks.xyz/api/tasks/cp1u6O9InfekT/close \
-H "Authorization: Bearer capri_YOUR_TOKEN"const res = await fetch('https://capritasks.xyz/api/tasks/cp1u6O9InfekT/close', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN' },
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/tasks/cp1u6O9InfekT/close",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/tasks/cp1u6O9InfekT/close");
std::cout << res.status << " " << res.body << "\n";
}{
"ok": true
}curl -X POST https://capritasks.xyz/api/tasks/cp1u6O9InfekT/reopen \
-H "Authorization: Bearer capri_YOUR_TOKEN"const res = await fetch('https://capritasks.xyz/api/tasks/cp1u6O9InfekT/reopen', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN' },
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/tasks/cp1u6O9InfekT/reopen",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/tasks/cp1u6O9InfekT/reopen");
std::cout << res.status << " " << res.body << "\n";
}{
"ok": true
}curl -X DELETE https://capritasks.xyz/api/tasks/cp1u6O9InfekT \
-H "Authorization: Bearer capri_YOUR_TOKEN"const res = await fetch('https://capritasks.xyz/api/tasks/cp1u6O9InfekT', {
method: 'DELETE',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN' },
});
const data = await res.json();import requests
res = requests.delete(
"https://capritasks.xyz/api/tasks/cp1u6O9InfekT",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->del("/api/tasks/cp1u6O9InfekT");
std::cout << res.status << " " << res.body << "\n";
}{
"ok": true
}Comments
curl https://capritasks.xyz/api/tasks/cp1u6O9InfekT/comments \
-H "Authorization: Bearer capri_YOUR_TOKEN"const res = await fetch('https://capritasks.xyz/api/tasks/cp1u6O9InfekT/comments', {
headers: { Authorization: 'Bearer capri_YOUR_TOKEN' },
});
const data = await res.json();import requests
res = requests.get(
"https://capritasks.xyz/api/tasks/cp1u6O9InfekT/comments",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->get("/api/tasks/cp1u6O9InfekT/comments");
std::cout << res.status << " " << res.body << "\n";
}{
"comments": [
{
"id": "cQgg1IysWiB5S",
"task_id": "cp1u6O9InfekT",
"content": "Numbers are in the shared folder",
"posted_at": "2026-11-09T20:27:12.763Z"
}
]
}Add a comment
Adds a comment to a task.
Body (JSON)
| Field | Type | What it is |
|---|---|---|
task_idrequired | string | The task. |
contentrequired | string | The comment. |
curl -X POST https://capritasks.xyz/api/comments \
-H "Authorization: Bearer capri_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"task_id": "cp1u6O9InfekT", "content": "Numbers are in the shared folder"}'const res = await fetch('https://capritasks.xyz/api/comments', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
"task_id": "cp1u6O9InfekT",
"content": "Numbers are in the shared folder"
}),
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/comments",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
json={
"task_id": "cp1u6O9InfekT",
"content": "Numbers are in the shared folder"
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/comments",
netcpp::Body::json(R"({"task_id": "cp1u6O9InfekT", "content": "Numbers are in the shared folder"})"));
std::cout << res.status << " " << res.body << "\n";
}{
"id": "cQgg1IysWiB5S",
"task_id": "cp1u6O9InfekT",
"content": "Numbers are in the shared folder",
"posted_at": "2026-11-09T20:27:12.763Z"
}Projects
Add a project
Makes a project.
Body (JSON)
| Field | Type | What it is |
|---|---|---|
namerequired | string | Its name. |
color | string | One of the colours. |
curl -X POST https://capritasks.xyz/api/projects \
-H "Authorization: Bearer capri_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Work", "color": "blue"}'const res = await fetch('https://capritasks.xyz/api/projects', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
"name": "Work",
"color": "blue"
}),
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/projects",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
json={
"name": "Work",
"color": "blue"
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/projects",
netcpp::Body::json(R"({"name": "Work", "color": "blue"})"));
std::cout << res.status << " " << res.body << "\n";
}{
"id": "ciy0N4oeU6DMn",
"name": "Work",
"color": "blue",
"parent_id": null,
"child_order": 1,
"inbox_project": false,
"is_archived": false,
"is_favorite": false,
"view_style": "list"
}Change a project
Renames or recolours it.
Body (JSON)
| Field | Type | What it is |
|---|---|---|
name | string | A new name. |
color | string | A new colour. |
curl -X POST https://capritasks.xyz/api/projects/ciy0N4oeU6DMn \
-H "Authorization: Bearer capri_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Work stuff"}'const res = await fetch('https://capritasks.xyz/api/projects/ciy0N4oeU6DMn', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
"name": "Work stuff"
}),
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/projects/ciy0N4oeU6DMn",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
json={
"name": "Work stuff"
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/projects/ciy0N4oeU6DMn",
netcpp::Body::json(R"({"name": "Work stuff"})"));
std::cout << res.status << " " << res.body << "\n";
}{
"id": "ciy0N4oeU6DMn",
"name": "Work stuff",
"color": "blue",
"parent_id": null,
"child_order": 1,
"inbox_project": false,
"is_archived": false,
"is_favorite": false,
"view_style": "list"
}Delete a project
Deletes it with its sections and tasks. This can’t be undone.
curl -X DELETE https://capritasks.xyz/api/projects/ciy0N4oeU6DMn \
-H "Authorization: Bearer capri_YOUR_TOKEN"const res = await fetch('https://capritasks.xyz/api/projects/ciy0N4oeU6DMn', {
method: 'DELETE',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN' },
});
const data = await res.json();import requests
res = requests.delete(
"https://capritasks.xyz/api/projects/ciy0N4oeU6DMn",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->del("/api/projects/ciy0N4oeU6DMn");
std::cout << res.status << " " << res.body << "\n";
}{
"ok": true
}Sections
Add a section
Makes a section in a project.
Body (JSON)
| Field | Type | What it is |
|---|---|---|
namerequired | string | Its name. |
project_idrequired | string | Which project. |
curl -X POST https://capritasks.xyz/api/sections \
-H "Authorization: Bearer capri_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "This week", "project_id": "ciy0N4oeU6DMn"}'const res = await fetch('https://capritasks.xyz/api/sections', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
"name": "This week",
"project_id": "ciy0N4oeU6DMn"
}),
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/sections",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
json={
"name": "This week",
"project_id": "ciy0N4oeU6DMn"
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/sections",
netcpp::Body::json(R"({"name": "This week", "project_id": "ciy0N4oeU6DMn"})"));
std::cout << res.status << " " << res.body << "\n";
}{
"id": "cKYAq921AUF6H",
"project_id": "ciy0N4oeU6DMn",
"name": "This week",
"section_order": 1,
"is_archived": false
}curl -X POST https://capritasks.xyz/api/sections/cKYAq921AUF6H \
-H "Authorization: Bearer capri_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Next week"}'const res = await fetch('https://capritasks.xyz/api/sections/cKYAq921AUF6H', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
"name": "Next week"
}),
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/sections/cKYAq921AUF6H",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
json={
"name": "Next week"
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/sections/cKYAq921AUF6H",
netcpp::Body::json(R"({"name": "Next week"})"));
std::cout << res.status << " " << res.body << "\n";
}{
"id": "cKYAq921AUF6H",
"project_id": "ciy0N4oeU6DMn",
"name": "Next week",
"section_order": 1,
"is_archived": false
}Reorder sections
Sets the order of a project’s sections.
Body (JSON)
| Field | Type | What it is |
|---|---|---|
sectionsrequired | object[] | Each { "id", "section_order" }, counting from 1. |
curl -X POST https://capritasks.xyz/api/sections/reorder \
-H "Authorization: Bearer capri_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"sections": [{"id": "cKYAq921AUF6H", "section_order": 1}, {"id": "cTq0sV1aFq2Lm", "section_order": 2}]}'const res = await fetch('https://capritasks.xyz/api/sections/reorder', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
"sections": [
{
"id": "cKYAq921AUF6H",
"section_order": 1
},
{
"id": "cTq0sV1aFq2Lm",
"section_order": 2
}
]
}),
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/sections/reorder",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
json={
"sections": [
{
"id": "cKYAq921AUF6H",
"section_order": 1
},
{
"id": "cTq0sV1aFq2Lm",
"section_order": 2
}
]
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/sections/reorder",
netcpp::Body::json(R"({"sections": [{"id": "cKYAq921AUF6H", "section_order": 1}, {"id": "cTq0sV1aFq2Lm", "section_order": 2}]})"));
std::cout << res.status << " " << res.body << "\n";
}{
"ok": true
}curl -X DELETE https://capritasks.xyz/api/sections/cKYAq921AUF6H \
-H "Authorization: Bearer capri_YOUR_TOKEN"const res = await fetch('https://capritasks.xyz/api/sections/cKYAq921AUF6H', {
method: 'DELETE',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN' },
});
const data = await res.json();import requests
res = requests.delete(
"https://capritasks.xyz/api/sections/cKYAq921AUF6H",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->del("/api/sections/cKYAq921AUF6H");
std::cout << res.status << " " << res.body << "\n";
}{
"ok": true
}Labels
Add a label
Makes a label.
Body (JSON)
| Field | Type | What it is |
|---|---|---|
namerequired | string | Its name, without the @. |
color | string | One of the colours. |
curl -X POST https://capritasks.xyz/api/labels \
-H "Authorization: Bearer capri_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "errands", "color": "green"}'const res = await fetch('https://capritasks.xyz/api/labels', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
"name": "errands",
"color": "green"
}),
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/labels",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
json={
"name": "errands",
"color": "green"
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/labels",
netcpp::Body::json(R"({"name": "errands", "color": "green"})"));
std::cout << res.status << " " << res.body << "\n";
}{
"id": "cxrxS24xnNyoY",
"name": "errands",
"color": "green",
"item_order": 1,
"is_favorite": false
}Change a label
Renames or recolours it.
Body (JSON)
| Field | Type | What it is |
|---|---|---|
name | string | A new name. |
color | string | A new colour. |
curl -X POST https://capritasks.xyz/api/labels/cxrxS24xnNyoY \
-H "Authorization: Bearer capri_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"color": "orange"}'const res = await fetch('https://capritasks.xyz/api/labels/cxrxS24xnNyoY', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
"color": "orange"
}),
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/labels/cxrxS24xnNyoY",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
json={
"color": "orange"
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/labels/cxrxS24xnNyoY",
netcpp::Body::json(R"({"color": "orange"})"));
std::cout << res.status << " " << res.body << "\n";
}{
"id": "cxrxS24xnNyoY",
"name": "errands",
"color": "orange",
"item_order": 1,
"is_favorite": false
}curl -X DELETE https://capritasks.xyz/api/labels/cxrxS24xnNyoY \
-H "Authorization: Bearer capri_YOUR_TOKEN"const res = await fetch('https://capritasks.xyz/api/labels/cxrxS24xnNyoY', {
method: 'DELETE',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN' },
});
const data = await res.json();import requests
res = requests.delete(
"https://capritasks.xyz/api/labels/cxrxS24xnNyoY",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->del("/api/labels/cxrxS24xnNyoY");
std::cout << res.status << " " << res.body << "\n";
}{
"ok": true
}Deadlines
List deadlines
Capri’s deadlines: the day a task must be done by, separate from its due date. Keyed by task id.
curl https://capritasks.xyz/api/deadlines \
-H "Authorization: Bearer capri_YOUR_TOKEN"const res = await fetch('https://capritasks.xyz/api/deadlines', {
headers: { Authorization: 'Bearer capri_YOUR_TOKEN' },
});
const data = await res.json();import requests
res = requests.get(
"https://capritasks.xyz/api/deadlines",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->get("/api/deadlines");
std::cout << res.status << " " << res.body << "\n";
}{
"cp1u6O9InfekT": "2026-12-01"
}Set deadlines
Sets or clears deadlines for any number of tasks at once.
Body (JSON)
| Field | Type | What it is |
|---|---|---|
setrequired | object | Task id → YYYY-MM-DD, or null to clear it. |
curl -X POST https://capritasks.xyz/api/deadlines \
-H "Authorization: Bearer capri_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"set": {"cp1u6O9InfekT": "2026-12-01", "cIg5KxTmKOBXk": null}}'const res = await fetch('https://capritasks.xyz/api/deadlines', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
"set": {
"cp1u6O9InfekT": "2026-12-01",
"cIg5KxTmKOBXk": null
}
}),
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/deadlines",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
json={
"set": {
"cp1u6O9InfekT": "2026-12-01",
"cIg5KxTmKOBXk": None
}
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/deadlines",
netcpp::Body::json(R"({"set": {"cp1u6O9InfekT": "2026-12-01", "cIg5KxTmKOBXk": null}})"));
std::cout << res.status << " " << res.body << "\n";
}{
"ok": true
}Schedule
Get the schedule
Your time-block schedule: repeating schedules (days 0 = Sunday to 6 = Saturday) and, per date, one-off extra blocks and the ids ticked off that day.
curl https://capritasks.xyz/api/schedule \
-H "Authorization: Bearer capri_YOUR_TOKEN"const res = await fetch('https://capritasks.xyz/api/schedule', {
headers: { Authorization: 'Bearer capri_YOUR_TOKEN' },
});
const data = await res.json();import requests
res = requests.get(
"https://capritasks.xyz/api/schedule",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->get("/api/schedule");
std::cout << res.status << " " << res.body << "\n";
}{
"schedules": [
{
"id": "weekdays",
"name": "Weekdays",
"days": [
1,
2,
3,
4,
5
],
"blocks": [
{
"id": "deep",
"title": "Deep work",
"start": "09:00",
"end": "11:30"
}
]
}
],
"days": {
"2026-11-10": {
"extra": [
{
"id": "gym",
"title": "Gym",
"start": "18:00",
"end": "19:00"
}
],
"done": [
"deep"
]
}
}
}Save the schedule
Needs Capri+. Send schedules to replace the repeating schedules, or day with extra and done to replace one date.
Body (JSON)
| Field | Type | What it is |
|---|---|---|
schedules | object[] | Up to 20, each { id, name, days, blocks }. A block is { id, title, start, end } with HH:MM times. |
day | string | A date, YYYY-MM-DD. |
extra | object[] | That date’s one-off blocks. |
done | string[] | Ids of the blocks ticked off that date. |
curl -X POST https://capritasks.xyz/api/schedule \
-H "Authorization: Bearer capri_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"day": "2026-11-10", "extra": [{"id": "gym", "title": "Gym", "start": "18:00", "end": "19:00"}], "done": ["deep"]}'const res = await fetch('https://capritasks.xyz/api/schedule', {
method: 'POST',
headers: { Authorization: 'Bearer capri_YOUR_TOKEN', 'Content-Type': 'application/json' },
body: JSON.stringify({
"day": "2026-11-10",
"extra": [
{
"id": "gym",
"title": "Gym",
"start": "18:00",
"end": "19:00"
}
],
"done": [
"deep"
]
}),
});
const data = await res.json();import requests
res = requests.post(
"https://capritasks.xyz/api/schedule",
headers={"Authorization": "Bearer capri_YOUR_TOKEN"},
json={
"day": "2026-11-10",
"extra": [
{
"id": "gym",
"title": "Gym",
"start": "18:00",
"end": "19:00"
}
],
"done": [
"deep"
]
},
)
data = res.json()#include <netcpp/netcpp.hpp>
#include <iostream>
int main() {
auto capri = netcpp::api("https://capritasks.xyz");
capri->bearer("capri_YOUR_TOKEN");
auto res = capri->post("/api/schedule",
netcpp::Body::json(R"({"day": "2026-11-10", "extra": [{"id": "gym", "title": "Gym", "start": "18:00", "end": "19:00"}], "done": ["deep"]})"));
std::cout << res.status << " " << res.body << "\n";
}{
"ok": true
}Objects
Replies use the same shapes as Todoist’s API, whichever kind of account you have.
Task
| Field | Type | What it is |
|---|---|---|
id | string | Its id. |
content | string | The name. |
description | string | Notes under the name. |
project_id | string | Its project. |
section_id | string | null | Its section, if any. |
parent_id | string | null | Its parent task, for a sub-task. |
labels | string[] | Label names. |
priority | integer | 4 is the most urgent (P1), 1 normal (P4). |
due | object | null | date (YYYY-MM-DD, or with a time), string (how it was written), is_recurring. |
checked | boolean | Whether it’s done. |
completed_at | string | null | When it was done. |
added_at | string | When it was made. |
child_order | integer | Its place in the list. |
note_count | integer | How many comments it has. |
Project
| Field | Type | What it is |
|---|---|---|
id | string | Its id. |
name | string | Its name. |
color | string | One of the colours below. |
inbox_project | boolean | true for the Inbox. |
child_order | integer | Its place in the sidebar. |
Section
| Field | Type | What it is |
|---|---|---|
id | string | Its id. |
project_id | string | Its project. |
name | string | Its name. |
section_order | integer | Its place in the project. |
Label
| Field | Type | What it is |
|---|---|---|
id | string | Its id. |
name | string | Its name, without the @. |
color | string | One of the colours below. |
Comment
| Field | Type | What it is |
|---|---|---|
id | string | Its id. |
task_id | string | Its task. |
content | string | The text. |
posted_at | string | When it was added. |
Colours
Projects and labels take one of these names as color.
berry_redredorangeyellowolive_greenlime_greengreenmint_greentealsky_bluelight_bluebluegrapevioletlavendermagentasalmoncharcoalgreytaupeErrors
Anything other than 200 comes with {"error": "…"}, a sentence saying what went wrong, which you can show to people as it is.
{
"error": "That API token isn’t valid. It may have been deleted, or ended with a password reset."
}
| Status | Means |
|---|---|
| 400 | Something in the request is wrong: a missing field, a bad date, an id that doesn’t exist. error says what. |
| 401 | The token is wrong, deleted, or ended with a password reset. |
| 402 | That needs Capri+ ("plus": true in the reply). Saving the schedule, say. |
| 403 | The token doesn’t have the permission this needs ("scope" in the reply says which), or tokens can’t use that address at all (account settings, Co-Work and so on). |
| 404 | No such task, project, section or label. |
| 429 | More than 120 requests in a minute. Wait a moment and try again. |
| 502 | Todoist didn’t answer, for an account synced with Todoist. Try again shortly. |