Tasks
Tasks are user-owned commitments that can belong to one or more agents (linkedIds). They can be one-shot (dueAt) or recurring (daily / weekly / monthly / cron). The platform fires due tasks into workflows registered to handle them; this API lets you create, manage, and observe tasks programmatically.
All endpoints require authentication via X-API-Key header and the appropriate scope.
Scopes
| Scope | Endpoints |
|---|---|
tasks:read | GET /primitives/tasks, GET /primitives/tasks/:id, GET /primitives/tasks/:id/permissions |
tasks:write | POST /primitives/tasks, PATCH /primitives/tasks/:id, DELETE /primitives/tasks/:id, POST /primitives/tasks/:id/complete, POST /primitives/tasks/:id/cancel, POST /primitives/tasks/:id/skip-next, POST /primitives/tasks/:id/end-recurrence, POST /primitives/tasks/:id/permissions/members, DELETE /primitives/tasks/:taskId/permissions/members/:userId, PATCH /primitives/tasks/:id/permissions |
Lifecycle
- One-shot tasks (no recurrence): status moves
open→fired→done/cancelled/failed. Terminal states cannot transition further. - Recurring tasks: status stays
openbetween fires. After each firedueAtadvances to the next slot,fireCountincrements, and a record is appended tofireHistory(capped at 50 entries -- older entries are dropped). POST /primitives/tasks/:id/completeon a recurring task closes the latestfireHistoryentry (status: "done",completedAtset) and advancesdueAtto the next slot.- Agent heartbeat: an agent claims a due heartbeat task before it works it (
claimedBy,claimedAt). Completing it counts that run:fireCountincrements andlastFiredAtis the claim time. A task with nodueAtornextDueAtis intent-only and no heartbeat runs it on its own. skip_next: truecauses the next fire to advancedueAtwithout invoking any workflow, then resets tofalse.POST /primitives/tasks/:id/end-recurrenceis terminal -- the recurring task moves to statusdoneandrecurrenceEndedAtis set.
Task Object
{
"_id": "task_abc123",
"ownerId": "user_456",
"ownerName": "Alice Example",
"organizationId": "org_789",
"linkedIds": ["wf_321"],
"sharedWith": [],
"isPublic": false,
"description": "Run the weekly compliance report",
"subjectRef": {
"kind": "report",
"id": "rpt_001",
"app": "compliance-suite"
},
"createdByAgent": null,
"assignedToAgent": "agent_compliance_bot",
"status": "open",
"triggerType": "heartbeat",
"dueAt": "2026-05-23T09:00:00Z",
"firedAt": null,
"completedAt": null,
"failureReason": null,
"notes": "Email the PDF to legal@example.com",
"recurrence": {
"kind": "weekly",
"timeOfDay": "09:00",
"dayOfWeek": 6,
"dayOfMonth": null,
"cron": null,
"timezone": "America/Los_Angeles",
"until": null,
"maxFires": null
},
"nextDueAt": "2026-05-23T09:00:00Z",
"lastFiredAt": "2026-05-16T09:00:00Z",
"fireCount": 4,
"fireHistory": [
{
"firedAt": "2026-05-16T09:00:00Z",
"completedAt": "2026-05-16T09:04:12Z",
"status": "done",
"executionId": "exec_aaa111"
}
],
"recurrenceEndedAt": null,
"skipNext": false,
"tags": ["compliance", "weekly"],
"createdAt": "2026-04-01T09:00:00Z",
"updatedAt": "2026-05-16T09:04:12Z"
}
GET /api/v1/primitives/tasks
List tasks the caller can read, with filters and pagination.
Scope: tasks:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | No | Only tasks in this status: open / fired / done / failed / cancelled. Without it, done, failed and cancelled tasks are left out unless includeCompleted=true |
kind | string | No | recurring (tasks with a recurrence) or oneoff (tasks without one) |
tags | string | No | Comma-separated tag list; a task must carry all of them |
linkedIds | string | No | Comma-separated agent IDs; a task must belong to all of them |
q | string | No | Case-insensitive substring match on description and notes |
includeCompleted | boolean | No | true also returns done, failed and cancelled tasks (default false). Has no effect when status is set |
dueOnly | boolean | No | true returns only tasks whose time has arrived (dueAt or nextDueAt at or before now); tasks with neither are left out (default false) |
triggerType | string | No | heartbeat or scheduled: only tasks with this trigger type. A task created without one counts as scheduled when its recurrence kind is cron, otherwise heartbeat |
limit | integer | No | Max results (default 50, max 200) |
cursor | string | No | meta.nextCursor of the previous page; omit for the first page |
Response 200 OK
{
"data": [ /* Task objects */ ],
"meta": {
"total": 12,
"limit": 50,
"nextCursor": null,
"requestId": "req_abc123"
}
}
Example
curl -H "X-API-Key: $STRONGLY_API_KEY" \
-H "Content-Type: application/json" \
"https://<your-instance>/api/v1/primitives/tasks?status=open&limit=20"
POST /api/v1/primitives/tasks
Create a new task. Pass recurrence to create a recurring task; omit it for a one-shot.
Scope: tasks:write
Request Body
{
"description": "Run the weekly compliance report",
"dueAt": "2026-05-23T09:00:00Z",
"linkedIds": ["wf_321"],
"assignedToAgent": "agent_compliance_bot",
"subjectRef": {
"kind": "report",
"id": "rpt_001",
"app": "compliance-suite"
},
"notes": "Email the PDF to legal@example.com",
"tags": ["compliance", "weekly"],
"recurrence": {
"kind": "weekly",
"timeOfDay": "09:00",
"dayOfWeek": 6,
"timezone": "America/Los_Angeles"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
description | string | Yes | One-line task description |
dueAt | string | No | ISO timestamp the one-shot task is due (ignored for recurring tasks -- first fire is computed from recurrence) |
linkedIds | string[] | No | Agent IDs the task belongs to |
triggerType | string | No | heartbeat (the agent completes it when it next wakes) or scheduled (fired at its exact time). Default: scheduled for a cron recurrence, otherwise heartbeat |
sharedWith | string[] | No | User IDs to share the task with |
isPublic | boolean | No | Make the task readable across the organization (default false) |
subjectRef | object | No | {kind, id, app?} -- what the task is about |
createdByAgent | string | No | Agent that authored the task |
assignedToAgent | string | No | A label a Task Due workflow trigger can filter on (exact match). Agents work the tasks they are linked to (linkedIds). |
notes | string | No | Free-form notes |
tags | string[] | No | Discoverability tags |
recurrence | object | No | Recurrence spec -- see TaskRecurrence |
Response 201 Created
{
"data": { "taskId": "task_abc123" },
"meta": { "requestId": "req_abc123" }
}
Example
curl -X POST \
-H "X-API-Key: $STRONGLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "Run the weekly compliance report",
"linkedIds": ["wf_321"],
"recurrence": { "kind": "weekly", "timeOfDay": "09:00", "dayOfWeek": 6, "timezone": "America/Los_Angeles" }
}' \
https://<your-instance>/api/v1/primitives/tasks
Returns 400 validation-error if description is missing, recurrence is invalid, or triggerType is not heartbeat or scheduled.
GET /api/v1/primitives/tasks/:id
Get a single task by ID.
Scope: tasks:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Task ID |
Response 200 OK
Returns the full Task object.
Example
curl -H "X-API-Key: $STRONGLY_API_KEY" \
-H "Content-Type: application/json" \
https://<your-instance>/api/v1/primitives/tasks/task_abc123
PATCH /api/v1/primitives/tasks/:id
Update a task. Only the fields supplied are changed.
Scope: tasks:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Task ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
description | string | No | One-line task description |
dueAt | string | null | No | ISO timestamp the task is due (one-shot only); null removes it |
assignedToAgent | string | null | No | Task Due trigger label (see create); null removes it |
subjectRef | object | null | No | {kind, id, app?}; null removes it |
notes | string | null | No | Free-form notes |
tags | string[] | No | Discoverability tags |
recurrence | object | null | No | Replace the recurrence spec; pass null to convert to one-shot |
Response 200 OK
The task, as GET /primitives/tasks/:id shows it.
DELETE /api/v1/primitives/tasks/:id
Delete a task and its fire history. Irreversible.
Scope: tasks:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Task ID |
Response 204 No Content
Example
curl -X DELETE \
-H "X-API-Key: $STRONGLY_API_KEY" \
-H "Content-Type: application/json" \
https://<your-instance>/api/v1/primitives/tasks/task_abc123
POST /api/v1/primitives/tasks/:id/complete
Mark the task done. On a one-shot task this is terminal (status: "done"). On a recurring task this closes the latest fireHistory entry and advances dueAt to the next slot -- the task stays open.
Scope: tasks:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Task ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
executionId | string | No | Execution that handled the fire -- written into the closed fireHistory entry |
notes | string | No | Completion notes; replaces the task's notes |
Response 200 OK
The task, as GET /primitives/tasks/:id shows it: status done for a one-off task; a recurring one has its nextDueAt.
POST /api/v1/primitives/tasks/:id/cancel
Mark the task as cancelled. Terminal for both one-shot and recurring tasks.
Scope: tasks:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Task ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | No | Free-form cancellation reason -- written to failureReason |
Response 200 OK: the task.
The task moves to status: "cancelled".
Example
curl -X POST \
-H "X-API-Key: $STRONGLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "reason": "Compliance report no longer required" }' \
https://<your-instance>/api/v1/primitives/tasks/task_abc123/cancel
POST /api/v1/primitives/tasks/:id/skip-next
Skip the next scheduled fire of a recurring task. Sets skip_next: true -- at the next scheduled slot, dueAt advances without invoking any workflow, and skipNext resets to false.
Scope: tasks:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Task ID |
Response 200 OK: the task, its nextFireAt moved past the skipped fire.
Example
curl -X POST \
-H "X-API-Key: $STRONGLY_API_KEY" \
-H "Content-Type: application/json" \
https://<your-instance>/api/v1/primitives/tasks/task_abc123/skip-next
Returns 400 validation-error if the task has no recurrence (one-shot tasks cannot be skipped).
POST /api/v1/primitives/tasks/:id/end-recurrence
Stop a recurring task. Terminal -- moves the task to status: "done" and sets recurrenceEndedAt to the server time of the call. No future fires will occur.
Scope: tasks:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Task ID |
Response 200 OK: the task.
The task moves to status: "done" and recurrenceEndedAt is set.
Example
curl -X POST \
-H "X-API-Key: $STRONGLY_API_KEY" \
-H "Content-Type: application/json" \
https://<your-instance>/api/v1/primitives/tasks/task_abc123/end-recurrence
Returns 400 validation-error if the task has no recurrence.
Sharing
Who can reach a task is the platform's one sharing shape: its owner, its members
(role editor can use and change it, user can only use it) and its visibility
(public: every user can find and use it; in a multi-tenant deployment, every user of
its organization). Changing it stays with its owner and editors.
| Method | Path | Does | Scope |
|---|---|---|---|
| GET | /api/v1/primitives/tasks/:id/permissions | Owner, members (userId, role) and visibility | tasks:read |
| POST | /api/v1/primitives/tasks/:id/permissions/members | Share with a user: { "userId", "role": "editor" | "user" } | tasks:write |
| DELETE | /api/v1/primitives/tasks/:taskId/permissions/members/:userId | Stop sharing with a user | tasks:write |
| PATCH | /api/v1/primitives/tasks/:id/permissions | { "visibility": "public" | "private" } | tasks:write |
Each, except a member's removal (204), answers the permissions as they are now:
{
"data": {
"resourceId": "<task id>",
"owner": "<user id>",
"members": [{ "userId": "<user id>", "role": "user" }],
"visibility": "private"
},
"meta": { "requestId": "req_abc123" }
}
POST /api/v1/primitives/tasks/:id/claim
Claim a task's current occurrence before doing it, so a task linked to several agents is done once. The claim lapses when its occurrence ends.
Scope: tasks:write
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Task id |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
claimantId | string | Yes | Your agent's id |
Response 200 OK: the task, its claimedBy you, when you won the claim (do the task); claiming again as the winner returns it again.
Error 409 Conflict: another agent has this occurrence (skip it); detail names it.
Schemas
TaskStatus
Enum string. Valid values:
| Value | Meaning |
|---|---|
open | Task is scheduled and waiting for its next fire. Default for recurring tasks between fires. |
fired | One-shot task has been dispatched to its workflow but has not yet completed. |
done | Terminal success state. For recurring tasks, set only by end-recurrence. |
failed | Terminal failure state. failureReason is set. |
cancelled | Terminal -- user-initiated cancellation. |
RecurrenceKind
Enum string. Valid values:
| Value | Description |
|---|---|
none | Not recurring (one-shot). Equivalent to omitting recurrence. |
daily | Fires every day at timeOfDay (in timezone). |
weekly | Fires every dayOfWeek at timeOfDay. |
monthly | Fires every dayOfMonth at timeOfDay. |
cron | Fires per arbitrary cron expression in timezone. |
TaskRecurrence
{
"kind": "weekly",
"timeOfDay": "09:00",
"dayOfWeek": 6,
"dayOfMonth": null,
"cron": null,
"timezone": "America/Los_Angeles",
"until": null,
"maxFires": null
}
| Field | Type | Required | Description |
|---|---|---|---|
kind | string | Yes | One of RecurrenceKind |
timeOfDay | string | No | HH:MM 24-hour clock (used by daily / weekly / monthly) |
dayOfWeek | integer | No | 0–6 (Sunday=0). Required for weekly. |
dayOfMonth | integer | No | 1–31. Required for monthly. |
cron | string | No | Cron expression. Required for cron. |
timezone | string | No | IANA timezone (e.g. America/Los_Angeles); defaults to UTC. |
until | string | null | No | ISO timestamp at which recurrence stops automatically. |
maxFires | integer | null | No | Stop after N total fires. |
TaskSubjectRef
{
"kind": "report",
"id": "rpt_001",
"app": "compliance-suite"
}
| Field | Type | Required | Description |
|---|---|---|---|
kind | string | Yes | Free-form subject type (e.g. report, ticket, document) |
id | string | Yes | Subject identifier within its kind |
app | string | No | Originating app slug -- used for cross-app references |
TaskFireRecord
{
"firedAt": "2026-05-16T09:00:00Z",
"completedAt": "2026-05-16T09:04:12Z",
"status": "done",
"executionId": "exec_aaa111"
}
| Field | Type | Required | Description |
|---|---|---|---|
firedAt | string | Yes | ISO timestamp the workflow was dispatched |
completedAt | string | null | No | ISO timestamp the fire reached a terminal state |
status | string | Yes | done / failed / cancelled / skipped |
executionId | string | null | No | Execution that handled the fire |
fireHistory retains at most the 50 most recent entries -- older entries are dropped as new fires append.