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](https://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:

```bash
export SYNDAI_API_KEY=sk_syndai_live_...
```

If you lose it, revoke it and make a new one. See [Authentication](/docs/api/authentication) for scopes and presets.

## 3. Pick a project

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

```bash
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>`:

```bash
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:

```json
{
  "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](/docs/api/concepts#idempotency-key).

## 5. Follow its events

The events route is a server-sent event stream:

```bash
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:

```bash
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:

```bash
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`:

```bash
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

- Learn the terms in [Concepts](/docs/api/concepts).
- Get pushed news in place of polling with [Webhooks](/docs/api/webhooks).
- Handle failures with [Errors](/docs/api/errors).
- See every route in the [API spec](/docs/api/spec).
