Developers
API documentation
Read and write tasks, lists, and workspaces from anywhere. Self-serve keys, REST/JSON, CORS-friendly. Everything you need to plug GetItDone into your reporting stack, internal tools, or automation.
https://getitdone.pinch.africa/api/v1
CORS: all origins
Getting started
The GetItDone API is a small, opinionated REST surface. Every request is authenticated with a bearer token you generate yourself from the dashboard. Responses are JSON.
- Sign in and open a workspace.
- Go to Settings → API.
- Click Generate key, give it a name, and copy the key immediately (shown once).
- Make your first request:
curl https://getitdone.pinch.africa/api/v1/me \
-H "Authorization: Bearer gid_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
You should see a JSON body with your user, workspace ID, and abilities.
Generating a key
Only workspace owners and admins can generate keys. Members can submit a request on the same page and an admin issues on their behalf.
Fields you can set at issue time:
- Name — a human label (e.g.
Reporting dashboard). Shown in the audit trail. - Expiry —
Never,30 days,90 days, or1 year. - Abilities — see Scoping & abilities. Default is full access to this workspace.
gid_... is displayed once at generation and never again — only a SHA-256 hash is stored server-side. If you lose the key, revoke it and generate a new one.
Keys can be revoked at any time. Owners of a key and workspace admins see the same revoke button in Settings → API.
Authentication
Every request must include your key in the Authorization header:
Authorization: Bearer gid_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Missing or malformed keys return 401 Unauthorized. Expired or revoked keys also return 401.
Keys are workspace-scoped: even if the issuing user belongs to multiple workspaces, the key only sees the workspace it was issued for. This makes it safe to hand a key to a subagency without exposing the parent agency's data.
Scoping & abilities
A key's abilities array determines what it can do. Pick the smallest set that gets the job done.
| Ability | Grants | Typical use |
|---|---|---|
| * | All read + write actions | Trusted internal automation, backup jobs. |
| read | GET on all resources | Reporting dashboards, BI exports. |
| write | Create + update tasks | Intake forms, Zapier-style triggers. |
| admin | Manage members & settings (reserved) | Provisioning workspaces at scale. |
If a call lacks the required ability the server responds 403 { "error": "Token lacks ability: X" }.
CORS & browsers
/api/v1/* responds with permissive CORS headers, so you can call it directly from any origin:
Access-Control-Allow-Origin: <echoes the Origin>
Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, Accept, X-Requested-With
Access-Control-Max-Age: 86400
Endpoints
All paths are relative to https://getitdone.pinch.africa/api/v1. Responses are JSON; timestamps are ISO 8601 (UTC).
GET/me
Returns the user attached to the key and the workspace it's scoped to. Useful as a health check.
curl https://getitdone.pinch.africa/api/v1/me \
-H "Authorization: Bearer $GID_KEY"
Response 200
{
"user": { "id": 42, "name": "Ada Lovelace", "email": "ada@example.com" },
"workspace_id": 7,
"abilities": ["*"]
}
GET/workspaces
Returns the workspace this key is scoped to (a list of one).
GET/workspaces/{id}
Workspace details including counts.
{
"id": 7,
"name": "Pinch Africa",
"slug": "pinch-africa",
"color": "pink",
"icon": "briefcase",
"plan": { "slug": "pro", "name": "Pro", "price_cents": 4900, "currency": "USD", "billing_period": "month" },
"counts": { "spaces": 6, "tasks": 421, "members": 14 }
}
GET/tasks
Paginated list of tasks in the key's workspace.
Query parameters
| Param | Type | Description |
|---|---|---|
| list_id | int | Restrict to a single list. |
| status | string | Match a status by name (case-sensitive). |
| assignee_id | int | Only tasks assigned to this user id. |
| open | bool | 1 excludes completed tasks. |
| per_page | int | Page size, default 25, max 100. |
| page | int | 1-indexed page number. |
curl "https://getitdone.pinch.africa/api/v1/tasks?open=1&per_page=50" -H "Authorization: Bearer $GID_KEY"
Response 200
{
"data": [
{
"id": 1042,
"title": "Ship new landing copy",
"description": "Final pass before launch.",
"list_id": 82,
"list_name": "Marketing Q3",
"status": "In Progress",
"priority": 2,
"due_date": "2026-07-15T00:00:00+00:00",
"completed_at": null,
"created_at": "2026-06-30T09:12:33+00:00"
}
],
"meta": { "total": 421, "page": 1, "pages": 9, "per_page": 50 }
}
POST/tasks
Creates a task in a list.
curl -X POST https://getitdone.pinch.africa/api/v1/tasks \
-H "Authorization: Bearer $GID_KEY" \
-H "Content-Type: application/json" \
-d '{
"list_id": 82,
"title": "Review homepage copy",
"description": "Final pass before launch",
"due_date": "2026-07-20",
"priority": 2
}'
Response 201 — { "id": 1044, "title": "Review homepage copy" }
Fields: list_id (required), title (required, ≤ 500 chars), description, priority 0–4, due_date (ISO 8601 date).
PATCH/tasks/{id}
Partial update. Any field you omit is left untouched.
curl -X PATCH https://getitdone.pinch.africa/api/v1/tasks/1044 \
-H "Authorization: Bearer $GID_KEY" \
-H "Content-Type: application/json" \
-d '{ "completed": true }'
Marking completed: true sets completed_at to now and progress to 100. Setting completed: false reopens the task.
DELETE/tasks/{id}
Soft-deletes the task. Returns 204 No Content.
Webhooks
Configure outgoing webhooks in Settings → API → Outgoing webhooks. Subscribe to any of:
task.created · task.updated · task.completed · task.deleted ·
list.created · list.updated · comment.created · status.changed
Payloads are POSTed as JSON to your URL. Every request is signed with an HMAC-SHA256 signature in the X-GetItDone-Signature header — compute over the raw body with your webhook's signing secret:
import hmac, hashlib
def verify(body: bytes, header: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)
Failed deliveries retry with backoff. After repeated failures a webhook is auto-disabled and the owner is notified.
Errors
Standard HTTP status codes.
| Code | Meaning |
|---|---|
| 200 | OK |
| 201 | Resource created |
| 204 | No content (delete) |
| 401 | Missing / invalid / expired token |
| 403 | Token lacks ability, or resource not in scope |
| 404 | Resource not found |
| 422 | Validation failed |
| 429 | Rate limited |
| 5xx | Server error — retry with backoff |
Validation error shape (422):
{
"message": "The title field is required.",
"errors": { "title": ["The title field is required."] }
}
Rate limits
Default is 60 requests/minute per key. Exceeded requests return 429 Too Many Requests. Enterprise plans get higher limits — contact us.
Every response includes X-RateLimit-Limit and X-RateLimit-Remaining. When throttled you'll also receive Retry-After (seconds).
SDKs & recipes
There is no official SDK yet — the API is small enough to use directly. Here are copy-paste starters for common languages:
Node.js (fetch)
const GID_KEY = process.env.GID_KEY;
const BASE = "https://getitdone.pinch.africa/api/v1";
async function gid(path, init = {}) {
const res = await fetch(BASE + path, {
...init,
headers: {
"Authorization": `Bearer ${GID_KEY}`,
"Content-Type": "application/json",
...(init.headers ?? {}),
},
});
if (!res.ok) throw new Error(`GID ${res.status}: ${await res.text()}`);
return res.status === 204 ? null : res.json();
}
// list open tasks
const { data } = await gid("/tasks?open=1&per_page=100");
// create a task
await gid("/tasks", {
method: "POST",
body: JSON.stringify({ list_id: 82, title: "Sync from CRM", due_date: "2026-08-01" }),
});
Python (requests)
import os, requests
BASE = "https://getitdone.pinch.africa/api/v1"
S = requests.Session()
S.headers.update({"Authorization": f"Bearer {os.environ['GID_KEY']}"})
r = S.get(f"{BASE}/tasks", params={"open": 1, "per_page": 100})
r.raise_for_status()
tasks = r.json()["data"]
S.post(f"{BASE}/tasks", json={
"list_id": 82,
"title": "Sync from CRM",
"due_date":"2026-08-01",
})
PHP (Laravel Http)
$gid = Http::withToken(env('GID_KEY'))
->baseUrl('https://getitdone.pinch.africa/api/v1')
->acceptJson();
$open = $gid->get('/tasks', ['open' => 1])->json('data');
$gid->post('/tasks', [
'list_id' => 82,
'title' => 'Sync from CRM',
'due_date' => '2026-08-01',
]);
Pagination loop
Follow meta.pages until you've drained the resource:
let page = 1, all = [];
while (true) {
const r = await gid(`/tasks?per_page=100&page=${page}`);
all.push(...r.data);
if (page >= r.meta.pages) break;
page++;
}
FAQ
Can I generate a key for a subagency without exposing my whole workspace?
Yes — each key is bound to a single workspace. If your subagency has its own workspace, generate the key there; the key can't see any other workspace, even if the same user belongs to both.
What happens to a key when the user who created it leaves the workspace?
The key is revoked automatically. If you need continuity, generate the key under a service account that stays in the workspace.
Can I rotate keys without downtime?
Yes — generate a new key, roll it out to your caller, then revoke the old one. Both remain valid in the overlap.
Is there a sandbox environment?
Not yet. For low-risk testing, create a scratch workspace and issue a key there.
How do I get more endpoints?
Tell us what you need. We ship the endpoints our customers ask for.
Have questions? Talk to us.
Create a workspace →