Agent tasks
Start one autonomous Terra turn over the API, a free-form instruction or a page build, and poll it to completion.
Agent tasks are the write half of the developer surface. A task is one autonomous Terra turn: you hand it an instruction or an opportunity, it runs in the brand's workspace the way a turn you started in chat would, and you poll a handle until it settles. build_page is the Build-now button, and prompt is a chat message with nobody watching it.
Every task is a real agent run: around five minutes, and it counts against
your usage. Creation answers 202 with a handle and never blocks, and one
task at a time per brand. A retried create is a second run at a second run's
cost, so poll first, then submit the next one.
The two commands
prompt takes a free-form instruction in plain language, such as "Add an About page with a team section" or "Rewrite the pricing FAQ for the new tiers". The agent decides which of its own tools it needs. There is no single route at the end of it, so its result is the chat transcript.
build_page takes an opportunity ref from the page-opportunities list and builds that page. It runs the same admission checks as the dashboard's Build-now button, and it is the only command that reports a resultingRoute.
Starting a task
POST /api/v1/public/brands/{brandSlug}/tasks/
curl -X POST https://api.iterant.ai/api/v1/public/brands/acme/tasks/ \
-H "Authorization: Bearer itr_kK4dQ8v…" \
-H "Content-Type: application/json" \
-d '{
"command": "prompt",
"prompt": "Add an About page with a team section."
}'prompt is required, must be non-blank, and caps at 32,768 characters. A longer brief is refused at creation.
The two argument fields are mutually exclusive. Sending the wrong one is a 400 naming the field you sent: prompt with command: "build_page", or opportunityRef with command: "prompt".
The 202
{
"taskId": "6f1d8a52-0b3e-4f7a-9c2d-5a1b7e4c8d90",
"command": "prompt",
"status": "running",
"chatUrl": "https://app.iterant.ai/acme/chat?chat=b28c4417-91ad-4de6-8f0c-1c7f2ad50e63",
"createdAt": "2026-07-26T09:14:03.221Z"
}The 202 means the work was accepted and is running. You hold a handle to poll and a thread to watch.
taskId is a UUID, and it is the one place on this surface a UUID is the primary handle.
chatUrl deep-links the dashboard thread the turn runs in. For a prompt task that transcript is the result, so put it in the Slack message or the CI summary where a human can read it.
Polling
GET /api/v1/public/brands/{brandSlug}/tasks/{taskId}/
curl https://api.iterant.ai/api/v1/public/brands/acme/tasks/6f1d8a52-0b3e-4f7a-9c2d-5a1b7e4c8d90/ \
-H "Authorization: Bearer itr_kK4dQ8v…"{
"taskId": "6f1d8a52-0b3e-4f7a-9c2d-5a1b7e4c8d90",
"command": "build_page",
"status": "succeeded",
"createdAt": "2026-07-26T09:14:03.221Z",
"finishedAt": "2026-07-26T09:19:41.008Z",
"resultingRoute": "/guides/otel-cost-control",
"chatUrl": "https://app.iterant.ai/acme/chat?chat=…"
}Poll every 30–60 seconds. A typical task settles in about five minutes.
status | Meaning |
|---|---|
running | Dispatched and in flight. Keep polling |
succeeded | The agent finished and reported success |
failed | The agent reported failure, or nothing ever came back |
There is no queued state. A task is dispatched the moment you create it, so it is running from the first read.
A free-form turn has no single route, so resultingRoute stays empty even on
success. Only build_page carries one back. If your integration needs a URL
out of a task, use build_page; if it needs to know what happened, read
chatUrl.
error is present only when status is failed. It is absent otherwise.
A task that has been running for more than two hours with no word from the agent is reported as failed, which frees the brand to start something else. The reap happens when you read the task, so the first poll past the deadline is the one that settles it.
Poll, then submit
The shape of a multi-task integration:
BASE=https://api.iterant.ai/api/v1/public/brands/acme
AUTH="Authorization: Bearer itr_kK4dQ8v…"
for ref in "$@"; do
task=$(curl -sS -X POST "$BASE/tasks/" -H "$AUTH" \
-H "Content-Type: application/json" \
-d "{\"command\":\"build_page\",\"opportunityRef\":\"$ref\"}")
id=$(echo "$task" | jq -r .taskId)
echo "started $ref as $id"
while true; do
sleep 30
state=$(curl -sS "$BASE/tasks/$id/" -H "$AUTH")
[ "$(echo "$state" | jq -r .status)" = "running" ] || break
done
echo "$state" | jq -r '"\(.status) \(.resultingRoute // "")"'
doneThe loop waits out a 409 and waits out a slow task. Re-POSTing in either case starts a second agent run.
One task at a time, per brand
A create while another task is running for that brand returns the error below. A page build started anywhere else, including in the dashboard, blocks it the same way.
{
"type": "client_error",
"errors": [
{
"code": "conflict",
"detail": "A task is already running for this brand. Wait for it to finish, then retry.",
"attr": null
}
]
}The agent holds one workspace per brand, so tasks run one after another. That is also why there is no batch endpoint: N items is N tasks submitted in sequence, which is what the loop above does, at a cost of N × several minutes.
Different brands are independent. An orchestrator with an org-scoped key can run one task per brand concurrently.
Listing tasks
GET /api/v1/public/brands/{brandSlug}/tasks/
Newest first, with the standard paging envelope: items, total, offset, limit, note.
curl "https://api.iterant.ai/api/v1/public/brands/acme/tasks/?status=running" \
-H "Authorization: Bearer itr_kK4dQ8v…"status filters to running, succeeded, or failed. limit is 1–50, default 20. Each row is the same object the detail route returns, refreshed on read.
?status=running is the cheapest way to check whether this brand is busy before you spend a create on a 409. It is not the whole answer, because a build someone started in the dashboard blocks the brand too and is not a task row. Treat a 409 as the authority.
Callbacks
Pass callbackUrl on create and Iterant POSTs the outcome there when the task settles, either way:
curl -X POST https://api.iterant.ai/api/v1/public/brands/acme/tasks/ \
-H "Authorization: Bearer itr_kK4dQ8v…" \
-H "Content-Type: application/json" \
-d '{
"command": "build_page",
"opportunityRef": "otel-cost-control-buyers-guide-3f2a91b4",
"callbackUrl": "https://hooks.example.com/iterant/tasks"
}'{
"taskId": "6f1d8a52-0b3e-4f7a-9c2d-5a1b7e4c8d90",
"status": "succeeded",
"chatUrl": "https://app.iterant.ai/acme/chat?chat=…",
"resultingRoute": "/guides/otel-cost-control",
"timestamp": "2026-07-26T09:19:41.008Z"
}resultingRoute is present only when there is one; error only on failure.
Iterant validates the URL when you create the task. It must be https://, it must not carry user:password@ credentials, and its hostname must resolve to a public address. Loopback, link-local, private, and cloud-metadata ranges are refused with a 400. Redirects are never followed at delivery time.
Delivery is one POST with a 10-second timeout, retried at most three times
with exponential backoff from 5 seconds, then abandoned with a log line. There
is no signature on the callback body, so treat an inbound POST as a hint
that something changed and confirm it with GET …/tasks/{taskId}/. Polling is
the authority on what happened.
Errors
429 is not implemented. Nothing throttles this surface yet. The one-task-per-brand guard is a workspace constraint and does not substitute for a rate limit. Rate limiting is planned, and a client that already backs off on 429 will need no change when it arrives.
What a task will not do
- It will not publish. The agent works in the brand's workspace. Putting a page on a live domain is a separate step in the dashboard, and no task argument changes that.
- It will not run in bulk. One task per call, one task per brand.
- It will not write back to the opportunity queue. Dismissing, scheduling, and re-opening an opportunity are dashboard-only.
The same thing over MCP
An agent reaches this surface through two tools: create_agent_task and get_agent_task, with the same commands, the same 409, and the same poll-then-submit instruction written into the tool descriptions. The MCP tool takes no callbackUrl.
MCP tools: create_agent_task, get_agent_task, and the nine reads.
REST API: conventions, the error envelope, and the read endpoints.
Last updated on