SyndaiSign inStart free

API quickstart

Make an API key, start a coding run with one call, follow its events and read the result.

This page takes you from no key to a finished run. You need curl and jq. Every call goes to https://api.syndai.ai.

1. Sign in

Go to app.syndai.ai and sign in. Your first sign-in makes a workspace for you. Keys, projects and runs all belong to that workspace.

2. Make an API key

In the app, open Settings, then Developer, then API keys. Click Create key. Give it a name and pick one access preset. Run lifecycle covers this page.

The key is shown once. Copy it into your shell:

export SYNDAI_API_KEY=sk_syndai_live_...

If you lose it, revoke it and make a new one. See Authentication for scopes and presets.

3. Pick a project

A run works on one project. List the ones your key can see:

curl -s https://api.syndai.ai/api/v1/external/coding/repos \
  -H "Authorization: Bearer $SYNDAI_API_KEY" \
  | jq '.repos[] | {id, provider, full_name, default_branch, status}'

Copy the id of the project you want. To use a GitHub repo, connect GitHub in the app first.

No project yet? Skip the id. In step 4, send "new_repository": {} in place of coding_repository_id. Syndai makes a new hosted project for the run. You can name it with "new_repository": {"name": "my-app"}.

4. Start a run

Put your project id in place of <REPO_ID>:

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 with {\"status\": \"ok\"}",
    "coding_repository_id": "<REPO_ID>",
    "external_request_id": "healthz-endpoint-001"
  }'

prompt and external_request_id are required. You need one of coding_repository_id or new_repository.

A new run answers 201 with the run and its project:

{
  "run": {
    "id": "01234567-89ab-cdef-0123-456789abcdef",
    "current_phase": "intake",
    "external_request_id": "healthz-endpoint-001"
  },
  "project": { "id": "…", "repo_name": "…" },
  "created": true
}

The real run has many more fields. Keep run.id for the next steps.

external_request_id is your idempotency key. Send the same key and body again and you get 409 with code IDEMPOTENCY_REPLAY. The body names the first run in existing_run_id, and the Location header points at it. No second run starts. The same key with a different body is 409 CONFLICT. See Concepts.

5. Follow its events

The events route is a server-sent 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"

Each message has an event name, such as coding.run.phase_changed, and an id. The id is a global_seq cursor that only goes up. To pick up where you left off, send the last id back as the Last-Event-ID header, or as the since_global_seq query value. You also see stream_heartbeat messages, which carry no news.

6. Answer a question

Sometimes the run needs you. Then it sends coding.run.question_required. Read the open question from the run:

curl -s "https://api.syndai.ai/api/v1/external/coding/runs/$RUN_ID" \
  -H "Authorization: Bearer $SYNDAI_API_KEY" \
  | jq '.pending_hitl_requests'

Each request has a hitl_request_id and a list of 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. Your key needs coding:runs:respond.

7. Read the result

Read the run until run.current_phase is completed, failed or cancelled:

curl -s "https://api.syndai.ai/api/v1/external/coding/runs/$RUN_ID" \
  -H "Authorization: Bearer $SYNDAI_API_KEY" \
  | jq '{phase: .run.current_phase, result_kind: .run.result_kind, pr_url: .run.pr_url, final_response}'

A completed run has a result_kind. It is pull_request when the run opened one, and pr_url links to it. It is no_changes when nothing needed to change.

final_response.content holds the run's own summary once final_response.status is ready. The coding.run.final_response_ready event tells you when. That text is only on this route. Events and webhooks never carry it.

To build on a finished run, start a new run. Send parent_coding_run_id with the first run's id, and a new external_request_id.

Next