Skip to content

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.

Terminal window
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"
}
FieldTypeNotes
runIdstringMatches the runId returned by the trigger.
agentIdstring | nullThe 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.
conversationIdstring | nullThe 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.
createdAtUtcstringISO-8601 timestamp when the trigger was accepted.
endedAtUtcstring | nullISO-8601 timestamp when the run reached a terminal status, or null while status is in_progress.
resultstring | nullA 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.
statusstringOne of in_progress, success, failure, cancelled.
in_progress ──▶ success
──▶ failure
──▶ cancelled
  • in_progress — the only non-terminal status. Continue polling.
  • success — the run completed; result holds the JSON-encoded final assistant payload, or is null if the run produced no assistant payload.
  • failure — the run errored. result is null; inspect the Kindo Terminal or the underlying conversation for diagnostics.
  • cancelled — the run was cancelled (manually or by a timeout). result is null.

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.

Terminal window
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"
}
FieldTypeNotes
runIdstringThe new run’s id — distinct from the run you restarted. Track and poll it the same way you would a freshly triggered run.
conversationIdstringThe 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 runId is 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 404 when the run does not exist or the caller lacks access — the same collapse-to-404 behavior as GET /v1/runs/{runId}.

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.

Terminal window
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."
}
]
}
FieldTypeNotes
runIdstringMatches the runId from the trigger.
resultstringAggregate 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.
stepsarrayOne entry per step in the run, ordered by stepNumber.

Step shape:

FieldTypeNotes
stepNumberinteger1-indexed position in the run.
namestring | nullDisplay name of the step, or null if unnamed.
resultstring | nullOne 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.
explanationstring | nullFree-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 (aggregate)MeaningWhen it arises
passAll evaluated objectives satisfied.Run reached success, every evaluated step verdict is satisfied.
failAt least one objective was not satisfied.Any step verdict is “not satisfied”.
pendingEvaluation not finished yet.Run still in_progress, or at least one step verdict has not been written.
partialThe 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.
skippedEvaluation did not run for any step.Run ended in failure or cancelled with no evaluated steps, or no step had an objective to evaluate.
unknownVerdicts 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.
StatusConditionBody shape
400runId is malformed (not a UUID).{ "error": "Bad Request", "message": "..." }
401Missing or invalid API key.{ "error": "Unauthorized", "message": "..." }
404Run does not exist or the caller lacks access (deliberately not distinguished).{ "error": "Not found", "message": "Run <runId> not found." }
500Unexpected 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."
}
ConditionTransient?Client action
result: "pending" / step result: nullYesKeep polling with backoff.
result: "partial" (run failure/cancelled)NoRun is terminal; read steps[] for the verdicts of the steps that completed.
500YesRetry with backoff; evals are idempotent.
400 (malformed runId)NoFix the request; do not retry as-is.
401NoProvide a valid API key; do not retry as-is.
404NoThe 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.

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_progress status as terminal and stop.

Kindo does not currently push webhooks for run completion; polling is the only delivery mechanism for the result.

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.

  • Quickstart — end-to-end discover → trigger → poll flow.
  • Request shape — the trigger and discovery bodies.
  • Errors404 Not found, 422 Unprocessable Entity, etc.