Authentication
Create an API key in Settings → API. Pass it as a Bearer token on every request. Keys are shown once — store them safely.
curl https://www.gumhop.com/api/v1/me \ -H "Authorization: Bearer gh_live_…"
Errors & rate limits
Errors return a JSON body { "error": "message" } with an appropriate status: 401 (invalid or missing key), 400 (bad input), 404 (not found), 429 (rate limit exceeded).
The API is capped at 120 requests per 60-second window per account(shared across all of that account's keys). Requests over the cap are rejected with 429 { "error": "rate limit exceeded" } — retry once the window resets.
Workspaces & teams
Gumhop has shared team workspaces, but the v1 API is scoped to the account that owns the key — it reads and writes only the folders, lists, and tasks you created. That spans your personal space and anything you authored in a team space, but it never includes content a teammate created in a shared workspace, even though the app shows that content to you.
GET /listsandGET /tasksreturn only your own rows. Filtering by a teammate'slist_idyields an empty result; a teammate's task id returns 404.POST /taskstargets a list you own — adding a task to a teammate's list returns 404. Omittinglist_iddrops the task into your first owned list.POST /listscreates the list in your Personal workspace, unless you nest it under afolder_idyou own in another workspace — then the list inherits that folder's workspace.- Every folder, list, and task carries a read-only
workspace_id. There are no v1 endpoints for workspaces, members, invites, assignees, comments, mentions, or notifications yet.
Endpoints
/meReturns the account the key belongs to.
{ "user_id": "uuid", "email": "you@example.com", "display_name": "Sara", "timezone": "America/New_York" }/listsEvery folder and list you own, each ordered by sidebar position. See Workspaces & teams for how this maps to shared spaces.
{
"folders": [{ "id": "uuid", "name": "Personal", "workspace_id": "uuid", "icon": null, "color": null, "sort_key": "a0" }],
"lists": [{ "id": "uuid", "name": "Work", "workspace_id": "uuid", "folder_id": null, "icon": null, "color": null, "sort_key": "a0" }]
}/listsCreate a list. Optional folder_id nests it under a folder you own. Returns 201 with the created { "list": { … } }.
curl -X POST https://www.gumhop.com/api/v1/lists \
-H "Authorization: Bearer gh_live_…" \
-H "Content-Type: application/json" \
-d '{"name":"Work","folder_id":null}'/tasks?list_id=&status=todoList tasks, ordered by sidebar position. Optional query params: list_id, status (todo/done). Returns { "tasks": [ … ] }; each task has this shape:
{
"id": "uuid", "workspace_id": "uuid", "list_id": "uuid", "title": "Renew passport",
"notes": null, "original_text": "renew passport",
"status": "todo", "priority": 3, "pinned": false,
"due_date": "2026-07-01", "due_time": null,
"chips": [], "tags": [], "ai_status": "none", "source": "api",
"completed_at": null, "created_at": "…", "updated_at": "…"
}tags and original_text are returned on reads but cannot be set through the API.
/tasks/:idFetch a single task by id. Returns { "task": { … } } with the same fields shown under GET /tasks.
/tasksCreate a task two ways. Mode A — natural language with AI parsing:
curl -X POST https://www.gumhop.com/api/v1/tasks \
-H "Authorization: Bearer gh_live_…" \
-H "Content-Type: application/json" \
-d '{"text":"email the deck to sara@acme.com friday","parse":true}'When you send text, AI parsing is on by default — the parse field is optional (pass {"parse":false} to store the raw text verbatim). The parser extracts a clean title, due date/time, priority, notes, and contact/location/link/meeting chips. Detected subtasks are not created via the API.
Parsing draws on a separate budget of ~40 parses/minute per account, shared with in-app capture. If you exceed it — or AI parsing is unavailable — the task is still created with your raw text as the title and no extracted fields, and the response is still 201.
Mode B — explicit structured fields:
-d '{"title":"Renew passport","due_date":"2026-07-01","priority":3}'Accepted fields: title, notes, list_id, due_date, due_time, priority (0-3, clamped), pinned, chips. Omit list_idto drop the task into your first list — it's an error to create a task when the account has no lists. New API tasks appear at the top of the list.
/tasks/:idUpdate fields. Setting {"status":"done"} completes a task; {"status":"todo"} reopens it and clears completed_at.
curl -X PATCH https://www.gumhop.com/api/v1/tasks/<id> \
-H "Authorization: Bearer gh_live_…" \
-H "Content-Type: application/json" \
-d '{"status":"done"}'/tasks/:idPermanently delete a task. Returns { "deleted": true }. Completed tasks may also be auto-deleted if you enable cleanup in Settings (off by default).
/rewards/completeClaim the gacha reward for a completed task — the same once-ever card draw the app fires when you check a task off (the Chrome extension uses this endpoint). Completing a task via PATCH /tasks/:id does not draw by itself; call this afterwards if you want the pull. Calling it on a still-open task marks the task done first.
curl -X POST https://www.gumhop.com/api/v1/rewards/complete \
-H "Authorization: Bearer gh_live_…" \
-H "Content-Type: application/json" \
-d '{"task_id":"<id>"}'A successful first claim returns the drawn card:
{
"ok": true,
"reward": {
"kind": "image", "imageUrl": "https://…", "message": "Nice work!",
"packName": "Treat yourself", "streakToday": 3, "rarity": "shiny"
},
"popup": true
}rarity is one of common / shiny / holographic / legendary. For media you uploaded, imageUrl is a short-lived signed URL (~10 minutes) — display it promptly rather than storing it; externally-hosted items pass their original URL through. popupmirrors the account's popup-celebration preference. Email/push delivery still follows your Settings, same as in-app completions.
A task rewards exactly once, ever: repeat calls return { "ok": true, "reward": null, "alreadyRewarded": true }. If no active pack has items yet, the claim is released and the response is { "ok": true, "reward": null, "empty": true } — the task can still reward later once a pack has cards.
Webhooks
Set a webhook URL in Settings → API. Gumhop POSTs task.created and task.completed events:
Webhooks are user-scoped, not workspace-scoped: each event is delivered only to the webhook URL of the task's owner — the account that created it. You receive events for tasks you created (including when a teammate completes one of yours), but never for tasks a teammate owns, even in a shared workspace.
{
"event": "task.completed",
"task": {
"id": "uuid", "title": "...", "status": "done",
"priority": 2, "pinned": false,
"due_date": "2026-07-01", "due_time": "14:30:00",
"notes": null, "source": "api", "ai_status": "none",
"list_id": "uuid", "chips": [], "completed_at": "...", "created_at": "..."
}
}The payload is a subset of the full task object — it omits tags, original_text, and updated_at, which are present on GET responses. source is api for tasks created via this API and app for tasks created in the app.
Verifying signatures
If you set a signing secret, each delivery includes an X-Gumhop-Signature-256 header: sha256=<hmac> of the raw body.
// Node.js
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}# Python
import hmac, hashlib
def verify(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(header, expected)Using Gumhop from an AI agent
- Create an API key and store it as a secret in your agent runtime.
- To capture a task, POST
/taskswith{"text":"…","parse":true}and let Gumhop extract a clean title, due date/time, priority, notes, and contact/location/link/meeting chips. Subtasks the parser detects are not created via the API. - To check progress, GET
/tasks?status=todo. - To complete a task, PATCH
/tasks/:idwith{"status":"done"}— which also fires yourtask.completedwebhook. - To give the user their card pull for that completion, POST
/rewards/completewith{"task_id":"…"}— idempotent, so it's always safe to call.