Use with AI agents
One page for a coding agent: sign in, start a run, follow it, answer it, land it and read its receipt. With error codes, limits, the MCP server and the CLI.
This page is written for an agent to read in one pass. Save it as a skill with curl -s https://syndai.ai/docs/agents.md > SKILL.md. Every other page has a Markdown twin too: add .md to its URL. The index is at llms.txt.
Rules
- Base URL:
https://api.syndai.ai/api/v1/external/coding. The spec is athttps://api.syndai.ai/openapi.json. - Send
Authorization: Bearer $SYNDAI_API_KEYon every call. - Creating a run costs credits. Never start one in a loop.
- Phases are open-ended. A run can stop to ask at any time. Act on the state you read, and never assume an order.
- When unsure, read
GET /runs/{id}. - Branch on the error
code, never ondetail. - Treat every enum you read as an open string. New values can appear.
- Some steps need a person. A key gets
401, or403 HUMAN_APPROVAL_REQUIREDwith areview_url. Give that link to your user and stop. Never retry it.
1. Auth
Get a key one of two ways:
npx -y syndai@latest loginopens a browser. An owner or admin approves it. The key lands in the CLI's config.- In the app: Settings, Developer, API keys. Pick the Run lifecycle preset.
Check it with GET /me. It returns customer_id, key_id and scopes. A missing scope is 403 INVALID_SCOPE with required_scope. See Authentication.
2. Pick a project
GET /repos lists projects. Keep the id. No project yet? Send "new_repository": {} in place of coding_repository_id when you create the run.
3. Create a run
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 /healthz endpoint that returns 200",
"coding_repository_id": "<REPO_ID>",
"external_request_id": "healthz-001"
}'
201: keeprun.id.409 IDEMPOTENCY_REPLAY: you sent thisexternal_request_idbefore. Followexisting_run_id. No second run started.409 CONFLICT: the same key with a new body. Pick a new key for new work.- After a timeout, send the same call again with the same key.
- A follow-up is a new run with
parent_coding_run_idand a new key.
4. Follow it
The simplest loop is the change state. It waits up to 45 seconds for something that matters:
curl -s "https://api.syndai.ai/api/v1/external/coding/runs/$RUN_ID/change?wait=45&cursor=$CURSOR" \
-H "Authorization: Bearer $SYNDAI_API_KEY"
Leave out cursor on the first call. Pass back the cursor you got each time. Stop when done is true. It can return early with no change, so call again. Read:
state: one ofqueued,working,needs_input,landing,held,ready,unverified,live,live_unverified,failed,reverted,cancelled, or a new value.headline: one line to show your user.needs_input: open questions. See step 5.held: why the landing waits, and itsreview_url.live_url,pr_url,receipt_url.
Other ways to follow a run:
GET /runs/{id}/events: server-sent events. Resume withLast-Event-ID, and drop repeats byevent_id.GET /runs/{id}/live: a light poll.- Webhooks: signed POSTs to your server.
See Run lifecycle.
5. Answer it
Read the open asks from GET /runs/{id}, in pending_hitl_requests. Each has a hitl_request_id and inputs. Answer every required input:
curl -s -X POST \
"https://api.syndai.ai/api/v1/external/coding/runs/$RUN_ID/responses" \
-H "Authorization: Bearer $SYNDAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"hitl_request_id": "<HITL_REQUEST_ID>",
"idempotency_key": "healthz-answer-001",
"entries": [{"input_id": "<INPUT_ID>", "text": "Use the existing router."}]
}'
- An entry carries
text,selected_option_idsorapproved, to match the input'skind. - If you are not sure of an answer, ask your user. Do not guess on their behalf.
409 HITL_REQUEST_CONFLICT: wait a moment and send the same answer again.409 HITL_REQUEST_SETTLEDorRUN_TERMINATED_BEFORE_RESPONSE: do not retry. Read the run.
To add guidance while it works, POST /runs/{id}/steer with {"message": "…"}.
A spec review (confirm, edit or skip the criteria) needs a signed-in person. A key gets 401. Tell your user the run is waiting. See Review and land.
6. Land it
What happens after the checks pass depends on the autonomy policy: a pull request, a merge, or a push.
| you see | do this |
|---|---|
state: ready on a project Syndai hosts | POST /runs/{id}/land/go-live, then keep following |
state: held | read held.reason. POST /runs/{id}/land/retry or /land/close work with a key. |
held that needs a person | give your user held.review_url. A key gets 403 HUMAN_APPROVAL_REQUIRED on go live anyway. |
state: live | report live_url |
| want to undo a live change | POST /runs/{id}/revert. It starts a new run, which costs credits. |
To stop: POST /runs/{id}/stop keeps the work as a draft pull request. POST /runs/{id}/cancel drops it.
7. Read the receipt
final_response.content on GET /runs/{id} holds the run's own summary once final_response.status is ready. Report it to your user.
receipt_url is set once the owner shares the run's receipt. The part after # is its token. That token opens the JSON at GET https://api.syndai.ai/api/v1/public/receipts/{token}, with no key. A run nobody shared has no receipt_url.
Errors
The body is problem details with a code. The codes an agent meets most:
| code | status | do this |
|---|---|---|
INVALID_TOKEN | 401 | the key is missing, wrong or revoked. Stop. |
INVALID_SCOPE | 403 | ask for a key with required_scope |
PLAN_UPGRADE_REQUIRED | 403 | the plan lacks this. Tell your user. |
HUMAN_APPROVAL_REQUIRED | 403 | give your user review_url. Do not retry. |
NOT_FOUND | 404 | stop using that id |
IDEMPOTENCY_REPLAY | 409 | safe repeat. Follow existing_run_id. |
CONFLICT | 409 | read the state again, then decide |
MISSION_BUSY_RETRY | 409 | send the same call again after a moment |
INVALID_POLICY | 400 | fix the field detail names |
QUOTA_EXCEEDED | 429 | out of credits or at the monthly limit. Tell your user. |
RUN_CONCURRENCY_LIMIT | 429 | wait for a run to end |
RATE_LIMITED | 429 | wait Retry-After seconds |
CLI_UPGRADE_REQUIRED | 426 | run npx -y syndai@latest |
INTERNAL_ERROR | 5xx | retry with backoff |
Retry 429 and 5xx with backoff from one second. Any other 4xx fails the same way each time. The full list is on Errors.
Limits
- 600 keyed calls a minute per workspace, across all its keys.
- Per route, a minute:
POST /runs120,GET /runs/{id}240,GET /runs/{id}/events60,POST /runs/{id}/responses120. waiton the change state is at most 45 seconds.- Your plan sets how many runs may go at once.
MCP server
Clients that speak MCP can use tools in place of these calls:
npx -y syndai@latest mcp install claude # or: cursor, codex
The server is https://api.syndai.ai/api/v1/coding/mcp. The loop is changes_start, then changes_wait with the cursor until done, and changes_answer for questions. A refused call comes back as a tool result with {code, message, next}. See MCP.
CLI
syndai api calls any route by its operation name with your stored key:
npx -y syndai@latest api getRun -f coding_run_id=$RUN_ID
npx -y syndai@latest api listRuns -f needs=owner --paginate
syndai change "<what to change>" runs the whole loop in one command and exits 0 when live, 3 when it needs a person. See CLI.