REST API for AI agents to create, update, and query tasks. All endpoints return JSON.
Every request (except GET /health, /, and /docs) must include the API key as a header.
x-api-key: YOUR_API_KEY
Missing or incorrect key returns 401 {"error": "Invalid or missing x-api-key"}.
| Field | Type | Description |
|---|---|---|
| id | string (UUID) | Auto-generated. Read-only. |
| title | string | required Short description of the task. |
| status | enum |
pending
in_progress
done
blocked
Default: pending
|
| priority | enum |
low
medium
high
urgent
Default: medium
|
| notes | string | null | Free-text context, progress updates, or error details. |
| action_needed | string | null | Set this when a human must intervene. Surfaces at the top of every email report and the dashboard. |
| agent_name | string | null | Identifier for the agent that owns this task (e.g. engagement-scanner). |
| created_at | ISO 8601 | Auto-set on create. Read-only. |
| updated_at | ISO 8601 | Auto-updated on every PATCH. Read-only. |
| completed_at | ISO 8601 | null | Auto-set when status becomes done. Cleared if status changes away from done. |
status: "done" are automatically deleted 24 hours after completed_at.{ "ok": true, "ts": "2026-04-30T12:00:00.000Z" }
?status=pending|in_progress|done|blocked.curl https://tryrelayapp.com/tasks \ -H "x-api-key: YOUR_KEY"
{
"total": 5,
"by_status": { "pending": 2, "in_progress": 1, "done": 1, "blocked": 1 },
"action_needed": [
{ "id": "uuid", "title": "Fix auth", "action_needed": "Rotate the key", "priority": "high" }
]
}
404 if not found.curl https://tryrelayapp.com/tasks/TASK_ID \ -H "x-api-key: YOUR_KEY"
201 with the created task. Only title is required.curl -X POST https://tryrelayapp.com/tasks \
-H "x-api-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Generate weekly digest",
"status": "in_progress",
"priority": "medium",
"notes": "Processing 47 posts from this week",
"agent_name": "weekly-digest-agent"
}'
status: "done" auto-sets completed_at.# Mark done
curl -X PATCH https://tryrelayapp.com/tasks/TASK_ID \
-H "x-api-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "done"}'
# Flag for human action
curl -X PATCH https://tryrelayapp.com/tasks/TASK_ID \
-H "x-api-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "blocked", "action_needed": "OAuth token expired โ needs manual re-auth"}'
204 No Content.curl -X DELETE https://tryrelayapp.com/tasks/TASK_ID \ -H "x-api-key: YOUR_KEY"
done tasks older than 24h. Also runs automatically at midnight ET.{ "removed": 3 }
?period=evening for the evening template (default: morning).curl -X POST "https://tryrelayapp.com/tasks/report?period=morning" \ -H "x-api-key: YOUR_KEY"
Drop this helper into any TypeScript/JavaScript agent:
const BASE = "https://tryrelayapp.com";
const KEY = process.env.TASK_TRACKER_API_KEY;
const h = { "x-api-key": KEY!, "Content-Type": "application/json" };
const tracker = {
async create(task: { title: string; priority?: string; status?: string; notes?: string; action_needed?: string; agent_name?: string }) {
const res = await fetch(`${BASE}/tasks`, { method: "POST", headers: h, body: JSON.stringify(task) });
const { task: t } = await res.json();
return t.id as string;
},
async update(id: string, patch: Record<string, string>) {
await fetch(`${BASE}/tasks/${id}`, { method: "PATCH", headers: h, body: JSON.stringify(patch) });
},
async done(id: string, notes?: string) {
await this.update(id, { status: "done", ...(notes ? { notes } : {}) });
},
async block(id: string, action_needed: string) {
await this.update(id, { status: "blocked", action_needed });
},
async summary() {
const res = await fetch(`${BASE}/tasks/summary`, { headers: h });
return res.json();
},
};
import requests, os
BASE = "https://tryrelayapp.com"
HEADERS = {"x-api-key": os.environ["TASK_TRACKER_API_KEY"], "Content-Type": "application/json"}
def create_task(title, status="pending", priority="medium", notes=None, action_needed=None, agent_name=None):
r = requests.post(f"{BASE}/tasks", headers=HEADERS, json={
"title": title, "status": status, "priority": priority,
"notes": notes, "action_needed": action_needed, "agent_name": agent_name
})
return r.json()["task"]["id"]
def update_task(task_id, **kwargs):
requests.patch(f"{BASE}/tasks/{task_id}", headers=HEADERS, json=kwargs)
def mark_done(task_id):
update_task(task_id, status="done")
def block_task(task_id, action_needed):
update_task(task_id, status="blocked", action_needed=action_needed)
Standard lifecycle for a long-running agent job:
// 1. Create task when job starts
const taskId = await tracker.create({
title: "Nightly lead scan โ 2026-04-30",
status: "in_progress",
priority: "medium",
agent_name: "nightly-scanner",
});
try {
// 2. Update notes as work progresses
await tracker.update(taskId, { notes: "Processed 142 Reddit posts, found 23 leads" });
// 3. Mark done when finished
await tracker.done(taskId, "Completed: 23 leads added to sheet, 3 emails sent");
} catch (err) {
// 4. Block + flag for human if something needs attention
await tracker.block(taskId, `Job failed: ${err.message} โ check API credentials`);
}