Skip to main content

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​

ScopeEndpoints
tasks:readGET /primitives/tasks, GET /primitives/tasks/:id, GET /primitives/tasks/:id/permissions
tasks:writePOST /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 open between fires. After each fire dueAt advances to the next slot, fireCount increments, and a record is appended to fireHistory (capped at 50 entries -- older entries are dropped).
  • POST /primitives/tasks/:id/complete on a recurring task closes the latest fireHistory entry (status: "done", completedAt set) and advances dueAt to the next slot.
  • Agent heartbeat: an agent claims a due heartbeat task before it works it (claimedBy, claimedAt). Completing it counts that run: fireCount increments and lastFiredAt is the claim time. A task with no dueAt or nextDueAt is intent-only and no heartbeat runs it on its own.
  • skip_next: true causes the next fire to advance dueAt without invoking any workflow, then resets to false.
  • POST /primitives/tasks/:id/end-recurrence is terminal -- the recurring task moves to status done and recurrenceEndedAt is 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

ParameterTypeRequiredDescription
statusstringNoOnly tasks in this status: open / fired / done / failed / cancelled. Without it, done, failed and cancelled tasks are left out unless includeCompleted=true
kindstringNorecurring (tasks with a recurrence) or oneoff (tasks without one)
tagsstringNoComma-separated tag list; a task must carry all of them
linkedIdsstringNoComma-separated agent IDs; a task must belong to all of them
qstringNoCase-insensitive substring match on description and notes
includeCompletedbooleanNotrue also returns done, failed and cancelled tasks (default false). Has no effect when status is set
dueOnlybooleanNotrue returns only tasks whose time has arrived (dueAt or nextDueAt at or before now); tasks with neither are left out (default false)
triggerTypestringNoheartbeat or scheduled: only tasks with this trigger type. A task created without one counts as scheduled when its recurrence kind is cron, otherwise heartbeat
limitintegerNoMax results (default 50, max 200)
cursorstringNometa.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"
}
}
FieldTypeRequiredDescription
descriptionstringYesOne-line task description
dueAtstringNoISO timestamp the one-shot task is due (ignored for recurring tasks -- first fire is computed from recurrence)
linkedIdsstring[]NoAgent IDs the task belongs to
triggerTypestringNoheartbeat (the agent completes it when it next wakes) or scheduled (fired at its exact time). Default: scheduled for a cron recurrence, otherwise heartbeat
sharedWithstring[]NoUser IDs to share the task with
isPublicbooleanNoMake the task readable across the organization (default false)
subjectRefobjectNo{kind, id, app?} -- what the task is about
createdByAgentstringNoAgent that authored the task
assignedToAgentstringNoA label a Task Due workflow trigger can filter on (exact match). Agents work the tasks they are linked to (linkedIds).
notesstringNoFree-form notes
tagsstring[]NoDiscoverability tags
recurrenceobjectNoRecurrence 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

ParameterTypeRequiredDescription
idstringYesTask 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

ParameterTypeRequiredDescription
idstringYesTask ID

Request Body

FieldTypeRequiredDescription
descriptionstringNoOne-line task description
dueAtstring | nullNoISO timestamp the task is due (one-shot only); null removes it
assignedToAgentstring | nullNoTask Due trigger label (see create); null removes it
subjectRefobject | nullNo{kind, id, app?}; null removes it
notesstring | nullNoFree-form notes
tagsstring[]NoDiscoverability tags
recurrenceobject | nullNoReplace 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

ParameterTypeRequiredDescription
idstringYesTask 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

ParameterTypeRequiredDescription
idstringYesTask ID

Request Body

FieldTypeRequiredDescription
executionIdstringNoExecution that handled the fire -- written into the closed fireHistory entry
notesstringNoCompletion 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

ParameterTypeRequiredDescription
idstringYesTask ID

Request Body

FieldTypeRequiredDescription
reasonstringNoFree-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

ParameterTypeRequiredDescription
idstringYesTask 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

ParameterTypeRequiredDescription
idstringYesTask 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.

MethodPathDoesScope
GET/api/v1/primitives/tasks/:id/permissionsOwner, members (userId, role) and visibilitytasks:read
POST/api/v1/primitives/tasks/:id/permissions/membersShare with a user: { "userId", "role": "editor" | "user" }tasks:write
DELETE/api/v1/primitives/tasks/:taskId/permissions/members/:userIdStop sharing with a usertasks: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

ParameterTypeRequiredDescription
idstringYesTask id

Request Body

FieldTypeRequiredDescription
claimantIdstringYesYour 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:

ValueMeaning
openTask is scheduled and waiting for its next fire. Default for recurring tasks between fires.
firedOne-shot task has been dispatched to its workflow but has not yet completed.
doneTerminal success state. For recurring tasks, set only by end-recurrence.
failedTerminal failure state. failureReason is set.
cancelledTerminal -- user-initiated cancellation.

RecurrenceKind​

Enum string. Valid values:

ValueDescription
noneNot recurring (one-shot). Equivalent to omitting recurrence.
dailyFires every day at timeOfDay (in timezone).
weeklyFires every dayOfWeek at timeOfDay.
monthlyFires every dayOfMonth at timeOfDay.
cronFires 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
}
FieldTypeRequiredDescription
kindstringYesOne of RecurrenceKind
timeOfDaystringNoHH:MM 24-hour clock (used by daily / weekly / monthly)
dayOfWeekintegerNo0–6 (Sunday=0). Required for weekly.
dayOfMonthintegerNo1–31. Required for monthly.
cronstringNoCron expression. Required for cron.
timezonestringNoIANA timezone (e.g. America/Los_Angeles); defaults to UTC.
untilstring | nullNoISO timestamp at which recurrence stops automatically.
maxFiresinteger | nullNoStop after N total fires.

TaskSubjectRef​

{
"kind": "report",
"id": "rpt_001",
"app": "compliance-suite"
}
FieldTypeRequiredDescription
kindstringYesFree-form subject type (e.g. report, ticket, document)
idstringYesSubject identifier within its kind
appstringNoOriginating 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"
}
FieldTypeRequiredDescription
firedAtstringYesISO timestamp the workflow was dispatched
completedAtstring | nullNoISO timestamp the fire reached a terminal state
statusstringYesdone / failed / cancelled / skipped
executionIdstring | nullNoExecution that handled the fire

fireHistory retains at most the 50 most recent entries -- older entries are dropped as new fires append.