Protocol spec
The rules a Syndai coding API client and server follow, written as MUST, SHOULD and MAY: keys, run states, repeat-safe requests, events, webhooks and errors.
This page lists the rules of the Syndai coding API. It does not depend on any one language. The other API pages explain each part in more depth.
The words MUST, MUST NOT, REQUIRED, SHALL, SHOULD, SHOULD NOT, RECOMMENDED, MAY and OPTIONAL mean what RFC 2119 says they mean.
All paths here sit under https://api.syndai.ai/api/v1/external/coding.
1. Keys
1.1. A client MUST send its key on every request as Authorization: Bearer <key>.
1.2. A client MUST treat a key as an opaque string. It MUST NOT read the key's text to guess what the key may do. GET /me tells it.
1.3. The server MUST check each request against the scope its route needs. A key without that scope MUST get 403 with code INVALID_SCOPE. A scope the workspace's plan does not include MUST get 403 with code PLAN_UPGRADE_REQUIRED.
1.4. The server MUST turn away a revoked key on its next request, with 401 and code INVALID_TOKEN.
1.5. The server MUST limit calls per workspace, and each route MAY have its own limit too. More keys MUST NOT raise a workspace's limit. Over a limit, the server MUST answer 429 and SHOULD send Retry-After.
See Authentication.
2. Run states
2.1. Each run MUST report a current_phase that the server sets. completed, failed and cancelled are the end phases. Other phase names MAY be added at any time.
2.2. A client MUST accept phase names it does not know. It MUST NOT treat the phase list as closed.
2.3. The server SHOULD send coding.run.phase_changed when a run moves to a new phase. A client SHOULD NOT guess a run's state from events that did not come. When it is unsure, it SHOULD read GET /runs/{id}.
2.4. A run in an end phase MUST NOT move to any other phase.
2.5. A client MAY cancel a run that has not ended. Work already pushed to the repo is not rolled back.
2.6. A completed run MAY have a result_kind of pull_request or no_changes, or none. result_kind is an open string. A client MUST NOT treat it as a phase.
2.7. GET /runs/{id} MUST return final_response.status. It is an open string. The values today are pending, ready, failed and unavailable.
readyMUST meanfinal_response.contentholds the text.readywins over any other state.- A run that has not ended reports
pending. A client SHOULD keep reading until the status is notpending. failedMUST mean the server kept a record that it could not publish the text. Itserror_codeisfinal_response_publication_failed.- An ended run with no text and no such record reports
unavailable.
2.8. The final message text MUST only be sent in final_response.content on GET /runs/{id}. It MUST NOT be copied into list rows, run summaries, artifacts, stream events or webhooks.
3. Repeat-safe requests
3.1. Making a run MUST be safe to repeat for each external_request_id in a workspace.
- Sending the same
external_request_idand the same request again MUST NOT make a second run or charge twice. The server MUST answer409with codeIDEMPOTENCY_REPLAY. The body MUST haveexisting_run_id, and theLocationheader MUST point at the first run. - A client MUST treat that
409as success, not as an error to retry. It SHOULD read the run thatLocationnames. - The server compares requests after it puts them in a standard form. Sending the same
external_request_idwith a different request MUST get409with codeCONFLICT. No run is made.
3.2. A client MUST use a new external_request_id for each new piece of work. Reusing one for new work gets the old run back. That is a client error, not a server error.
3.3. An external_request_id belongs to one workspace. Two workspaces may use the same value without clashing.
3.4. An answer to a question (POST /runs/{id}/responses) MUST carry an idempotency_key. Sending the same idempotency_key for the same hitl_request_id again MUST NOT change the run twice.
4. Events and the stream
4.1. Each event has a global_seq. It comes from one log shared by all runs. So for one run it goes up over time but skips numbers. A client MAY use it to order events and to drop repeats. A client MUST NOT treat a jump in global_seq as a lost event.
4.2. A client MUST NOT expect events from different runs to come in any set order.
4.3. GET /runs/{id}/events is a Server-Sent Events stream. It first sends the run's stored events, then new ones as they happen. Each event's SSE id is its global_seq.
4.4. To pick up after a break, a client SHOULD send the last id it got as Last-Event-ID. It MAY send ?since_global_seq= instead. The stream then sends only newer events.
4.5. The stream MAY send stream_heartbeat events when there is nothing new. A client MUST NOT act on them.
4.6. coding.run.completed means the repo work ended in success. It MUST carry no final message text. coding.run.final_response_ready means the text can be read. Its fields MUST be only message_id and result_kind. coding.run.final_response_failed MUST carry only error_code and result_kind.
5. Webhooks
5.1. Delivery is at least once. A client MUST handle the same event twice without harm. The body's event_id stays the same on every retry and redelivery. A client SHOULD use it to drop repeats.
5.2. Each delivery MUST carry a Syndai-Signature header of the form t=<unix seconds>,v1=<hex>. v1 MUST be the HMAC-SHA256 of {t}.{raw body}, keyed with the whole signing secret. A client MUST check it before it trusts the body.
5.3. A client SHOULD turn away a delivery whose t is more than 300 seconds from its own clock.
5.4. A client SHOULD answer with a 2xx within 10 seconds. Any other answer, or no answer in that time, counts as a failed try.
5.5. The server MUST retry a failed try, up to 8 tries in all. It waits 30 seconds before the second try and doubles the wait each time after. After the eighth failed try, the delivery is failed.
5.6. A failed webhook MUST NOT stop a run or make it fail.
5.7. An endpoint with no event_types, or an empty list, MUST get every event type, including ones added later. An endpoint with a list MUST get only those types. The server MUST NOT add new types to that list.
5.8. A client MUST ignore event types it does not know.
See Webhooks.
6. Errors
6.1. Every error MUST use the application/problem+json content type (RFC 7807).
6.2. Every error body MUST have a code. A client MUST use code to tell errors apart. The detail text is for people and MAY change at any time.
6.3. A 400 means the client must fix the request. A client MUST NOT send the same 400 request again unchanged.
6.4. After a 429, a client MUST wait for the Retry-After time when the header is there. When it is not, the client MUST back off, with a wait that grows each time.
6.5. A client SHOULD retry only 408, 429, 500, 502, 503 and 504, with backoff. It SHOULD wait for Retry-After when it is there. POST /runs is safe to retry with the same external_request_id, as section 3 says.
6.6. A client MUST NOT retry CUSTOMER_DISABLED, INVALID_SCOPE or REPO_NOT_BOUND. A person has to fix these first.
See Errors.