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 ──▶ in_progress (re-run failed step) ──▶ cancelledfailure is not a permanent sink for a run that failed on a step:
re-running the failed step from the Kindo app returns the run to
in_progress. A run that failed to start has no failed step, so it stays
failure; restarting it creates a new run (see the failure note below).
in_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. A run that failed to start has no turn to inspect; its run view in the Kindo app names the reason. An applicable budget can cause a failure; see Budget refusals. A run that failed on a step is not permanently terminal: a user can re-run the failed step from the Kindo app, which returns the same run toin_progress(and resetsendedAtUtctonull) until it reaches a terminal status again. A run that failed to start has no failed step to re-run, so it staysfailure. Retry it withPOST /v1/runs/{runId}/restart, which starts a new run with a newrunIdfrom the original inputs — the same action as the Kindo app’s Restart run button. The response does not say which kind of failure occurred, and restarting is valid for either.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.
For a budget refusal, wait for the block to lift before restarting.
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 | cancelled is terminal; a failure run that failed on a step can be re-run from the Kindo app (returns to in_progress), and any failure run can be restarted as a new run. 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. If you re-read afailurerun later, it may have returned toin_progressbecause a user re-ran its failed step from the Kindo app. A run that failed to start never returns toin_progress; its restart is a new run.
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.
