Syndai Docs

Run lifecycle

Start a run safely, follow it as events or a light poll, steer, stop or cancel it, rename it, and undo a change that went live.

A run is one task on one project. This page walks through a run from the first call to the last. Each step links to its page in the API reference, which lists every field.

Every path below starts with https://api.syndai.ai/api/v1/external/coding. Every call sends Authorization: Bearer $SYNDAI_API_KEY.

1. Start it

POST /runs starts the work. It costs credits, so it is safe to retry only with the same key.

curl -s -X POST https://api.syndai.ai/api/v1/external/coding/runs \
  -H "Authorization: Bearer $SYNDAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Add a contact form to the home page",
    "coding_repository_id": "<REPO_ID>",
    "external_request_id": "contact-form-001",
    "title": "Contact form"
  }'

external_request_id is your idempotency key. Pick one per piece of work, and keep it.

  • The same key and body again: 409 IDEMPOTENCY_REPLAY. The body has existing_run_id, and Location points at the run. No second run starts.
  • The same key with a new body: 409 CONFLICT.
  • A network timeout: send the same call again and follow existing_run_id.

A follow-up is a new run. Send parent_coding_run_id and a new key. See Create a run for every field.

2. Follow it

There are four ways to watch a run. Pick the one that fits your client.

routeuse it for
GET /runs/{id}/eventsa live stream of events (server-sent events)
GET /runs/{id}/changeone short state, and a wait of up to 45 s for the next change
GET /runs/{id}/livea light poll: goal, activity, questions, steer messages
GET /runs/{id}the full state, when you need to decide what to do

Phases are open-ended. A run can stop to ask you something at any time. So act on the state you read, not on the order you expect.

The event stream

curl -s --no-buffer \
  -H "Authorization: Bearer $SYNDAI_API_KEY" \
  -H "Accept: text/event-stream" \
  "https://api.syndai.ai/api/v1/external/coding/runs/$RUN_ID/events"

The stream sends the run's past events first, then new ones as they happen.

  • Each event has a name, such as coding.run.phase_changed, and an event_id.
  • New events carry an SSE id. Past events do not.
  • stream_heartbeat messages carry no news. They carry the current id.
  • To pick up where you left off, send the last id as Last-Event-ID.
  • An event can come twice across a reconnect. Drop repeats by event_id.
  • stream_reset means live events are down for now. Read GET /runs/{id}, then connect again.

The events are the same ones webhooks send. See Stream a run's events.

The change state

GET /runs/{id}/change returns a short ChangeState: state, a headline, open questions in needs_input, a held block, live_url and done. Pass the last cursor with wait (at most 45) and the call waits until something that matters moves. It can come back early with no change. Then call again. This is the loop the CLI and the MCP server use. See Get a run's change state.

The timeline

GET /runs/{run_id}/timeline lists every event the run recorded, each with its time and sequence. Use it to look back over a run that has ended. See Get a run's timeline.

3. Answer it

When a run needs you, it sends coding.run.question_required. Read pending_hitl_requests from GET /runs/{id}. Answer each one with POST /runs/{id}/responses, as the quickstart shows. Send an idempotency_key, so a retry is safe.

A run can also wait for a person to review its spec. That is in Review and land.

4. Steer, stop or cancel it

callwhat happens to the workscope
POST /runs/{id}/steerThe agent reads your message at its next turn. The run goes on.coding:runs:respond
POST /runs/{id}/stopThe run stops, most often within a minute. It saves its work so far as a draft pull request.coding:runs:cancel
POST /runs/{id}/cancelThe run stops and drops its work. Nothing is delivered.coding:runs:cancel

Steer a run in progress:

curl -s -X POST \
  "https://api.syndai.ai/api/v1/external/coding/runs/$RUN_ID/steer" \
  -H "Authorization: Bearer $SYNDAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "Keep the form to name, email and message."}'

A few rules:

  • Steering a run that has ended is 409. Start a follow-up run.
  • Stop answers 202 right away and ends a moment later. Follow the run until it ends.
  • A second stop, or a stop or cancel on a run that ended, changes nothing.
  • A 409 on cancel means the run is busy. Wait a moment and send it again.

5. Rename it

POST /runs/{id}/rename with {"title": "…"} changes the name people see. It never changes the work or the external_request_id. See Rename a run.

6. Read what it made

  • run.result_kind is pull_request or no_changes once the run completes. run.pr_url links to the pull request.
  • final_response.content is the run's own summary, once final_response.status is ready.
  • GET /runs/{id}/proposed-diff returns the diff it delivered.
  • run.land says whether the change went live. See Review and land.

7. Undo a change that went live

POST /runs/{id}/revert starts a new run that undoes the change. It is work like any run, so it costs credits. Call it again while that revert is open and you get the same one.

curl -s -X POST \
  "https://api.syndai.ai/api/v1/external/coding/runs/$RUN_ID/revert" \
  -H "Authorization: Bearer $SYNDAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"force": false}'
  • 409 if the change never landed.
  • 409 if newer changes landed on top of it. Send "force": true to revert it anyway.
  • 404 if the run did not land the change itself.

The answer has the new run and its land. Follow that run like any other. See Revert a landed change.

On this page