Syndai Docs

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 at https://api.syndai.ai/openapi.json.
  • Send Authorization: Bearer $SYNDAI_API_KEY on 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 on detail.
  • Treat every enum you read as an open string. New values can appear.
  • Some steps need a person. A key gets 401, or 403 HUMAN_APPROVAL_REQUIRED with a review_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 login opens 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: keep run.id.
  • 409 IDEMPOTENCY_REPLAY: you sent this external_request_id before. Follow existing_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_id and 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 of queued, 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 its review_url.
  • live_url, pr_url, receipt_url.

Other ways to follow a run:

  • GET /runs/{id}/events: server-sent events. Resume with Last-Event-ID, and drop repeats by event_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_ids or approved, to match the input's kind.
  • 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_SETTLED or RUN_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 seedo this
state: ready on a project Syndai hostsPOST /runs/{id}/land/go-live, then keep following
state: heldread held.reason. POST /runs/{id}/land/retry or /land/close work with a key.
held that needs a persongive your user held.review_url. A key gets 403 HUMAN_APPROVAL_REQUIRED on go live anyway.
state: livereport live_url
want to undo a live changePOST /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:

codestatusdo this
INVALID_TOKEN401the key is missing, wrong or revoked. Stop.
INVALID_SCOPE403ask for a key with required_scope
PLAN_UPGRADE_REQUIRED403the plan lacks this. Tell your user.
HUMAN_APPROVAL_REQUIRED403give your user review_url. Do not retry.
NOT_FOUND404stop using that id
IDEMPOTENCY_REPLAY409safe repeat. Follow existing_run_id.
CONFLICT409read the state again, then decide
MISSION_BUSY_RETRY409send the same call again after a moment
INVALID_POLICY400fix the field detail names
QUOTA_EXCEEDED429out of credits or at the monthly limit. Tell your user.
RUN_CONCURRENCY_LIMIT429wait for a run to end
RATE_LIMITED429wait Retry-After seconds
CLI_UPGRADE_REQUIRED426run npx -y syndai@latest
INTERNAL_ERROR5xxretry 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 /runs 120, GET /runs/{id} 240, GET /runs/{id}/events 60, POST /runs/{id}/responses 120.
  • wait on 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.

On this page