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.

Stable · v1 Base: 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.

  1. Sign in and open a workspace.
  2. Go to Settings → API.
  3. Click Generate key, give it a name, and copy the key immediately (shown once).
  4. Make your first request:
bash
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, or 1 year.
  • Abilities — see Scoping & abilities. Default is full access to this workspace.
Show once. The plaintext key 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:

bash
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 actionsTrusted internal automation, backup jobs.
readGET on all resourcesReporting dashboards, BI exports.
writeCreate + update tasksIntake forms, Zapier-style triggers.
adminManage 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:

bash
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
Don't ship keys in browser bundles. Anyone who views your site's source will see the key. For browser use, put a thin proxy on your own backend that injects the key server-side.

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.

bash
curl https://getitdone.pinch.africa/api/v1/me \
  -H "Authorization: Bearer $GID_KEY"

Response 200

json
{
  "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.

json
{
  "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

ParamTypeDescription
list_idintRestrict to a single list.
statusstringMatch a status by name (case-sensitive).
assignee_idintOnly tasks assigned to this user id.
openbool1 excludes completed tasks.
per_pageintPage size, default 25, max 100.
pageint1-indexed page number.
bash
curl "https://getitdone.pinch.africa/api/v1/tasks?open=1&per_page=50" -H "Authorization: Bearer $GID_KEY"

Response 200

json
{
  "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.

bash
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.

bash
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:

python
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.

CodeMeaning
200OK
201Resource created
204No content (delete)
401Missing / invalid / expired token
403Token lacks ability, or resource not in scope
404Resource not found
422Validation failed
429Rate limited
5xxServer error — retry with backoff

Validation error shape (422):

json
{
  "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)

javascript
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)

python
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)

php
$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:

javascript
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.