Agents API runs
POST /v1/agents/runs is asynchronous. The trigger returns
immediately with a runId; the result lands on
GET /v1/runs/{runId} when the run finishes.
GET /v1/runs/{runId}
Section titled “GET /v1/runs/{runId}”curl https://api.kindo.ai/v1/runs/7a7ced7d-95a8-416f-95bb-5c0ff3599f53 \ -H "Authorization: Bearer $KINDO_API_KEY"Response:
{ "runId": "7a7ced7d-95a8-416f-95bb-5c0ff3599f53", "agentId": "18d20df4-d9b3-4cb0-a009-9f11ec9c5d3d", "conversationId": "clx9a2b1c0000d4e5f6g7h8i9", "createdAtUtc": "2026-03-12T15:00:00.000Z", "endedAtUtc": "2026-03-12T15:00:08.000Z", "result": "{\"role\":\"assistant\",\"parts\":[{\"type\":\"text\",\"text\":\"Security scan complete. No critical findings.\"}]}", "status": "success"}| Field | Type | Notes |
|---|---|---|
runId | string | Matches the runId returned by the trigger. |
agentId | string | null | The agent that produced this run. null when the underlying agent has been soft-deleted; the run record remains visible but no longer resolves to a live agent. |
conversationId | string | null | The conversation backing this run. Pass it to GET /v1/conversations/{conversation_id}/items to read the full message history — useful for multi-turn continuity. Always present in practice today; typed as nullable for forward compatibility, so defensive clients should tolerate null. |
createdAtUtc | string | ISO-8601 timestamp when the trigger was accepted. |
endedAtUtc | string | null | ISO-8601 timestamp when the run reached a terminal status, or null while status is in_progress. |
result | string | null | A JSON-encoded string of the final assistant message payload — parse with JSON.parse to recover { role, parts, ... }. null while in_progress, always null on failure or cancelled, and also null on success when the run produced no assistant payload. |
status | string | One of in_progress, success, failure, cancelled. |
Status lifecycle
Section titled “Status lifecycle”in_progress ──▶ success ──▶ failure ──▶ cancelledin_progress— the only non-terminal status. Continue polling.success— the run completed;resultholds the JSON-encoded final assistant payload, or isnullif the run produced no assistant payload.failure— the run errored.resultisnull; inspect the Kindo Terminal or the underlying conversation for diagnostics.cancelled— the run was cancelled (manually or by a timeout).resultisnull.
POST /v1/runs/{runId}/restart
Section titled “POST /v1/runs/{runId}/restart”Re-runs a previous run by its runId — handy for retrying a run that
failed — without re-sending the original inputs. Like the trigger,
the call is asynchronous and returns immediately with a new
runId; poll GET /v1/runs/{newRunId} for its result.
curl -X POST https://api.kindo.ai/v1/runs/7a7ced7d-95a8-416f-95bb-5c0ff3599f53/restart \ -H "Authorization: Bearer $KINDO_API_KEY"Response (202 Accepted):
{ "runId": "b2f1c0a9-6e3d-4f28-9a17-2d4e8c1b7f60", "conversationId": "clx9a2b1c0000d4e5f6g7h8i9"}| Field | Type | Notes |
|---|---|---|
runId | string | The new run’s id — distinct from the run you restarted. Track and poll it the same way you would a freshly triggered run. |
conversationId | string | The conversation backing the new run. Pass it to GET /v1/conversations/{conversation_id}/items to read its history. |
Notes:
- No request body is required, and none is read — the new run re-uses the source run’s original inputs as they were first submitted.
- The source run is left untouched; the returned
runIdis a separate run with its own status, result, and conversation. - The new run uses the agent’s latest version, so its behavior can differ from the source run if the agent was edited in between.
- Returns
404when the run does not exist or the caller lacks access — the same collapse-to-404 behavior asGET /v1/runs/{runId}.
GET /v1/runs/{runId}/evals
Section titled “GET /v1/runs/{runId}/evals”Returns the aggregate evaluation result and per-step verdicts for a
run — including runs that failed or were cancelled partway, which
surface a partial result for the steps that were evaluated. Each
agent step can have an objective; this endpoint reports whether those
objectives were met.
curl https://api.kindo.ai/v1/runs/7a7ced7d-95a8-416f-95bb-5c0ff3599f53/evals \ -H "Authorization: Bearer $KINDO_API_KEY"Response:
{ "runId": "7a7ced7d-95a8-416f-95bb-5c0ff3599f53", "result": "fail", "steps": [ { "stepNumber": 1, "name": "Perform Web Search for Trending Stock", "result": "pass", "explanation": "Kindo performed web searches and synthesized findings into a clear summary." }, { "stepNumber": 2, "name": "Generate Stock Forecast", "result": "pass", "explanation": "Kindo delivered a comprehensive forecast with analyst targets and risk factors." }, { "stepNumber": 3, "name": "Execute Trade on Robinhood", "result": "fail", "explanation": "Kindo cannot execute trades on Robinhood — it lacks brokerage integration." } ]}| Field | Type | Notes |
|---|---|---|
runId | string | Matches the runId from the trigger. |
result | string | Aggregate eval result. One of pass, fail, partial, pending, skipped, unknown. pass = all objectives satisfied; fail = at least one failed; partial = run failed/cancelled but some completed steps were evaluated (inspect steps[]); pending = run in progress or verdicts not yet written; skipped = run failed/cancelled with no evaluated steps; unknown = no definitive result. |
steps | array | One entry per step in the run, ordered by stepNumber. |
Step shape:
| Field | Type | Notes |
|---|---|---|
stepNumber | integer | 1-indexed position in the run. |
name | string | null | Display name of the step, or null if unnamed. |
result | string | null | One of pass, fail, not_evaluated, unknown, or null. pass = objective satisfied; fail = objective not satisfied; not_evaluated = evaluation did not run; unknown = verdict could not be determined. null when no verdict has been recorded yet. |
explanation | string | null | Free-form explanation of the verdict; null when not evaluated. |
The evals endpoint returns 404 when the run does not exist or the
caller lacks access — the same collapse-to-404 behavior as
GET /v1/runs/{runId}.
Result values and what produces them
Section titled “Result values and what produces them”result (aggregate) | Meaning | When it arises |
|---|---|---|
pass | All evaluated objectives satisfied. | Run reached success, every evaluated step verdict is satisfied. |
fail | At least one objective was not satisfied. | Any step verdict is “not satisfied”. |
pending | Evaluation not finished yet. | Run still in_progress, or at least one step verdict has not been written. |
partial | The run did not complete, but some steps that ran were evaluated. | Run ended in failure or cancelled and at least one completed step has a verdict. Inspect steps[] for the per-step results; the run never reached success, so no clean pass/fail aggregate is claimed. |
skipped | Evaluation did not run for any step. | Run ended in failure or cancelled with no evaluated steps, or no step had an objective to evaluate. |
unknown | Verdicts exist but no definitive aggregate could be determined. | A verdict could not be determined for a step (and no step failed). |
The step-level result distinguishes two superficially similar cases:
not_evaluated— the step had no objective evaluation performed (the step itself recorded a “not evaluated” verdict).unknown— an evaluation was attempted but the verdict could not be determined.null— no verdict has been recorded for the step yet (see the async section above); this is transient.
Error responses
Section titled “Error responses”| Status | Condition | Body shape |
|---|---|---|
400 | runId is malformed (not a UUID). | { "error": "Bad Request", "message": "..." } |
401 | Missing or invalid API key. | { "error": "Unauthorized", "message": "..." } |
404 | Run does not exist or the caller lacks access (deliberately not distinguished). | { "error": "Not found", "message": "Run <runId> not found." } |
500 | Unexpected server-side error. Transient — retry with backoff. | { "error": "Internal Server Error", "message": "Something went wrong." } |
Example 404:
{ "error": "Not found", "message": "Run 7a7ced7d-95a8-416f-95bb-5c0ff3599f53 not found."}Example 500:
{ "error": "Internal Server Error", "message": "Something went wrong."}What to expect on failure
Section titled “What to expect on failure”| Condition | Transient? | Client action |
|---|---|---|
result: "pending" / step result: null | Yes | Keep polling with backoff. |
result: "partial" (run failure/cancelled) | No | Run is terminal; read steps[] for the verdicts of the steps that completed. |
500 | Yes | Retry with backoff; evals are idempotent. |
400 (malformed runId) | No | Fix the request; do not retry as-is. |
401 | No | Provide a valid API key; do not retry as-is. |
404 | No | The run is missing or inaccessible; do not retry as-is. |
There are no webhooks for evaluation completion — polling is the only mechanism. Re-fetching evals is always safe.
Polling cadence
Section titled “Polling cadence”Runs are typically long-running (seconds to minutes), so a short poll-then-back-off works well in practice:
- Wait 1–2 seconds before the first poll.
- Back off to 5–10 seconds for subsequent polls.
- Treat any non-
in_progressstatusas terminal and stop.
Kindo does not currently push webhooks for run completion; polling is the only delivery mechanism for the result.
Runs and conversations
Section titled “Runs and conversations”Every run is backed by its own server-side conversation. The
runId itself is not a conversation_id, but the conversationId
is returned directly on both the POST /v1/agents/runs trigger
response and the GET /v1/runs/{runId} run-result response. Pass it
to the Conversations API
(GET /v1/conversations/{conversation_id}/items) to read the full
message history — the basis for multi-turn continuity across runs.
For most use cases, result on GET /v1/runs/{runId} is the only
field you need.
See also
Section titled “See also”- Quickstart — end-to-end discover → trigger → poll flow.
- Request shape — the trigger and discovery bodies.
- Errors —
404 Not found,422 Unprocessable Entity, etc.