Runs

A run is one request to an agent. The same run, three ways of getting it back.

Three shapes, one body

All three start endpoints take the same request body and differ only in how the answer reaches you. Choose per call — there is nothing to configure.

  • POST /v1/agents/{agentId}/runs — stream. Server-Sent Events, token by token, including every tool call. Use it when a human is watching; see Streaming.
  • POST /v1/agents/{agentId}/runs/await — await. One JSON response when the run finishes. Simplest to integrate; the request is open for the whole run, so it suits short work. It answers 200 even when the run failed, so read status and errorCode:
    200 application/json
    {
      "runId": "01936c2f-…",
      "status": "completed",
      "message": "Three themes dominate …",
      "usage": { "inputTokens": 1841, "outputTokens": 612, "totalTokens": 2453 },
      "errorCode": null,
      "failureReason": null,
      "traceId": null
    }
    If you hang up before it finishes, the run is stopped and recorded as cancelled — unless you sent an Idempotency-Key: then it finishes, and your retry finds it.
  • POST /v1/agents/{agentId}/runs/submit — submit. Returns 202 and a run id immediately; the run executes in the background and you read it back with GET /v1/runs/{runId}. Use it for long work and for anything triggered by a webhook, where holding a connection is not an option. A submitted run carries no attachments: attachmentFileIds is ignored on it. It also takes no per-request mcpServers, and its mcpAuth is not kept, so a registered server that needs a token has no token on a submitted run; the 202 lists those tools in toolWarnings.

All three take an optional Idempotency-Key header: send the same call again with the same key and no second run starts and nothing is billed twice (Limits, Idempotency keys).

The streaming call is the one to avoid hanging up on: its run carries on whatever your client does, and nothing re-attaches you to it. The awaited call is the opposite — hang up without an Idempotency-Key and the run is stopped.

The request body

application/json
{
  "message": "required — what you are asking",
  "endUserRef": "your-own-user-id",
  "conversationId": "…",
  "threadConversationHistory": true,
  "model": "…",
  "tools": ["web_search"],
  "attachmentFileIds": ["…"],
  "mcpServers": [ … ],
  "mcpAuth": [ { "serverSlug": "…", "token": "…" } ],
  "userTimeZone": "Africa/Johannesburg"
}

Only message is required (at most 8,000 characters). The rest are worth knowing about:

  • endUserRef — attributes the run to one of your own users; at most 200 printable ASCII characters. Use an opaque id of your own, never an email, a name or a phone number; it is kept in audit entries for 12 months (see Data and privacy). A run without one is unattributed, and the agent's run-history tools refuse to read for it.
  • conversationId — groups runs into one conversation you can read back (below). On its own it does not replay earlier turns to the agent: each run sees only its own message. A conversation belongs to the API client and endUserRef that first use it: starting a run under one that already holds someone else's run is a 404, so mint a fresh id per chat.
  • threadConversationHistory — set true to have the agent remember the chat: it is replayed the earlier questions and answers of your runs with the same conversationId and endUserRef, never anyone else's. The newest 20 runs and 48,000 characters at most; older runs are left out whole. Needs both fields, or it is a 400. A run that pauses for an approval, a question or its background tasks and is resumed gets the same earlier chat back, under the same limits.
  • model — run on this model instead of the agent's. A model id: letters, digits and . _ - : / @, starting with a letter or digit, at most 100 characters. It must have a price on record (model_unpriced otherwise) and satisfy your organisation's privacy policy; see Errors.
  • tools — the complete list of tools for this run; at most 32 names. Leave it out and the run gets the agent's own tools and none of your MCP tools. Send it and the run gets exactly the names you list: to add your MCP tools and keep the agent's own (memory's recall and remember, web search, and so on), name those too. Each tool's name is on the agent's page in the Console, under Tool approvals. A name the agent does not have, or a handle no enabled server binds, is a 400 rather than ignored. A run that goes ahead without some of your tools answers with toolWarnings; see Tools and MCP.
  • attachmentFileIds — files to put in front of the agent, at most 8; see Files.
  • mcpServers / mcpAuth — your own tools, declared per request (at most 16 of each); see Tools and MCP.
  • workspaceId — not used on hosted Amy; leave it out.
  • userTimeZone — an IANA zone name such as Africa/Johannesburg, so the agent's clock and its answers about times use your user's zone. Omitted means UTC. A name that does not resolve is a 400.

Submit and poll

shell
# 1. hand it off
curl -X POST https://api.askamy.co.za/v1/agents/amy/runs/submit \
  -H "X-Api-Key: $AMY_API_KEY" -H "Content-Type: application/json" \
  -d '{"message": "Reconcile last month'"'"'s ledger and summarise the exceptions."}'
# → 202 {"runId":"01936c2f-…","status":"pending"}

# 2. read it back whenever
curl https://api.askamy.co.za/v1/runs/01936c2f-… -H "X-Api-Key: $AMY_API_KEY"

GET /v1/runs/{runId} returns the whole persisted run — status, the messages exchanged, token usage, any attachments it produced, and any approval it is waiting on. It is the same view whether the run streamed, awaited or was submitted. A submitted run reads pending until it ends, including while it is being worked on, so poll for a final status.

The statuses a run can have:

  • Still going: pending, running. And paused, waiting on someone: awaiting_approval (decide it, see Approvals) and awaiting_work (the run is waiting on background tasks it started).
  • Finished well: completed.
  • Stopped short, with what it had: budget_exceeded, truncated, expired. These are policy stops, not errors.
  • Ended without an answer: failed (read errorCode and failureReason) and cancelled.

Every route keyed by a run id — this read, its file downloads, the approval resume, cancel and retry — answers only the API client that started the run, with any of its keys. Another client's run in the same tenant is a 404, exactly like an id that does not exist. One exception: a team run's assignment approval. Team runs are a Console feature; if you do not use them, ignore this. No client starts a team run, so an approval that belongs to one is not tied to a client.

Cancel and retry

shell
# stop a run
curl -X POST https://api.askamy.co.za/v1/runs/01936c2f-…/cancel -H "X-Api-Key: $AMY_API_KEY"
# → 200 {"cancelled":true,"status":"running"}

# re-drive a run that failed with a retryable code (streams, like a start)
curl -N -X POST https://api.askamy.co.za/v1/runs/01936c2f-…/retry -H "X-Api-Key: $AMY_API_KEY"
  • POST /v1/runs/{runId}/cancel — stops a run. The answer is { "cancelled", "status" }, and status is the status before the stop took effect, so poll the run for cancelled. A queued run is cancelled on the spot. cancelled: false is not an error: the run had already ended, it is paused on an approval (decide that instead), it is waiting on its background tasks, or it is a task another run dispatched. Repeating a cancel is safe.
  • POST /v1/runs/{runId}/retry — re-drives a run that ended failed with a retryable code, from the messages it already persisted. No tool runs again; the run is reopened (it goes back to running and its failure fields clear) rather than copied. Its response streams exactly like a start. The body is optional; send mcpAuth and mcpServers again if the run used them. Check retryable on the run first: it is true only for a retryable code, while the run is the latest turn of its conversation and its agent is active, and fewer than retryCap retries are used (retriesUsed counts them). Any refusal is a 409 retry_refused with a reason, before the stream opens — see Errors. A retry is a new model call, so it passes the same plan, credential and pricing checks as a new run.

Reading a conversation back

Amy keeps the transcript, so your product does not have to. Both reads name one endUserRef, and return only runs your own API client started — with any of its keys, so a rotated key reads what the old one wrote. A conversation id another client reused shows you nothing of theirs.

shell
# one user's conversations, most recently active first
curl "https://api.askamy.co.za/v1/conversations?endUserRef=your-own-user-id&page=1&pageSize=50" \
  -H "X-Api-Key: $AMY_API_KEY"
# → {"items":[{"conversationId":"…","agentSlug":"amy","title":"…","status":"completed",
#      "startedAt":"…","lastActivityAt":"…","runCount":3}],"page":1,"pageSize":50,"total":1}

# one conversation's turns, in order
curl "https://api.askamy.co.za/v1/conversations/…?endUserRef=your-own-user-id" -H "X-Api-Key: $AMY_API_KEY"
# → {…,"turns":[{"runId":"…","role":"user","text":"…","createdAt":"…"},
#               {"runId":"…","role":"assistant","text":"…","createdAt":"…"}]}
  • GET /v1/conversations — that user's conversations, most recently active first, paged with page (from 1) and pageSize (default 50, at most 100). Each carries its title, its latest run's status, when it started and was last active, and how many runs it holds.
  • GET /v1/conversations/{conversationId} — the user and assistant turns, in order. A user turn is a run's message as the agent received it (a run that carried files ends with an [Attached files: …] line) or a note sent with an approval. Tool calls, their results and the messages Amy hands the agent itself, such as background-task results, are not part of it. A conversation that is not yours, or not this user's, is a 404.

Runs that stop early

A run does not always finish on its own. It can pause for a human decision (see Approvals), stop because your token budget ran out (budget_exceeded), be cut short (truncated), or fail. The first two are policy stops carrying the partial answer, not errors. Read status rather than inferring success from having received a terminal frame.