API errors
How the Syndai API reports an error: the problem details body, every error code with its HTTP status, and what your client should do next.
When the API says no, it sends a problem details body (RFC 7807). The content type is application/problem+json. The only routes that answer in a different shape are the two syndai login routes. See Device login errors.
The error body
{
"type": "about:blank",
"title": "Admission denied",
"status": 429,
"code": "RUN_CONCURRENCY_LIMIT",
"detail": "Customer concurrent run limit reached",
"instance": "https://api.syndai.ai/api/v1/external/coding/runs",
"reason": "concurrency_limit",
"active_runs": 3,
"max_concurrent_runs": 3
}
| field | what it holds |
|---|---|
type | always about:blank |
title | a short name for the error |
status | the HTTP status |
code | the error code. Your client should branch on this. |
detail | one line for a person to read. The words can change, so never parse them. |
instance | the URL you called |
existing_run_id | on IDEMPOTENCY_REPLAY for a run: the run you already made |
Some codes add fields of their own beside these:
| field | sent with |
|---|---|
reason | a refused run (BUDGET_EXHAUSTED, QUOTA_EXCEEDED, RUN_CONCURRENCY_LIMIT, CUSTOMER_DISABLED, and RATE_LIMITED from the daily spend limit), or OVERSCOPE_ACK_REQUIRED |
active_runs, max_concurrent_runs | RUN_CONCURRENCY_LIMIT |
required_scope | INVALID_SCOPE: the scope your key lacks |
review_url | HUMAN_APPROVAL_REQUIRED: the page where the owner decides |
refusal, run_id, prompt | RUN_IN_FLIGHT and TEXT_EDIT_REFUSED |
Error codes
| code | status | what it means | what to do |
|---|---|---|---|
BUDGET_EXHAUSTED | 402 | Syndai refused to start the work for a reason with no code of its own, such as a plan that is not active or a budget that is not valid. reason says which. | Read reason and detail. Fix the plan or budget, then try again. |
QUOTA_EXCEEDED | 429 | The workspace is out of credits, or it hit its monthly spend limit. | Add credits or wait for the next month. Don't retry in a loop. |
RUN_CONCURRENCY_LIMIT | 429 | The workspace already has as many runs going as its plan allows. | Wait for a run to end, or cancel one, then try again. |
CUSTOMER_DISABLED | 403 | The workspace is turned off. | Contact Syndai support. Don't retry. |
REPO_NOT_BOUND | 422 | PUT /policy/defaults named a coding_repository_id that is not in this workspace. | Check the id with GET /repos. |
IDEMPOTENCY_REPLAY | 409 | Your request id matched one you sent before. Location points at what you already made. A run also sends existing_run_id. | This is a safe repeat. Read what Location points at. Use a new request id for new work. |
CONFLICT | 409 | The request clashes with the current state of the run or thing you changed. | Read it again, then decide if the change still makes sense. |
MISSION_BUSY_RETRY | 409 | Another write held the run for a moment. Nothing changed. | Wait a moment and send the same request again. |
HITL_REQUEST_CONFLICT | 409 | The run can't take your answer yet. Nothing changed. | Wait a moment and send the same answer again. |
HITL_REQUEST_SETTLED | 409 | The question was already answered, timed out, or is closed. | Don't retry. Read the run again. |
RUN_TERMINATED_BEFORE_RESPONSE | 409 | The run ended before your answer got there. | Don't retry. Read the run again. |
INVALID_POLICY | 400 | The request body was not valid. This is most often a bad policy_config. detail names the field. | Fix the body. See Policy. |
INVALID_SCOPE | 403 | Your key lacks the scope this route needs. required_scope names it. | Make a key with that scope. |
RATE_LIMITED | 429 | Too many calls in a short time, or the workspace hit its daily spend limit. It may send Retry-After. | Wait the Retry-After seconds when it is there. Back off, and don't retry at once. |
INSTALLATION_INCOMPLETE | 422 | Part of the code list. No route sends it today. | None. |
SUBSCRIPTION_USAGE_UNAVAILABLE | 400 | Syndai could not check your subscription's usage before the run. | Try again in a little while. |
INVALID_TOKEN | 400/401 | 401: the API key is missing, wrong or revoked. 400: the invite token is not valid or was used. | Use a working key, or ask for a new invite. |
ROLE_REQUIRED | 403 | You are signed in, but your role can't do this. Only owners and admins can. | Ask an owner or admin. Signing in again won't help. |
PLAN_UPGRADE_REQUIRED | 403 | The workspace plan doesn't include this feature. | Upgrade the plan. A new key won't help. |
EMAIL_MISMATCH | 403 | You are signed in with an email that is not the one on the invite. | Sign in with the invited email, or ask for a new invite. |
EXPIRED_TOKEN | 410 | The invite has expired, or a syndai login code expired or was used. | Ask for a new invite, or run syndai login again. |
NOT_FOUND | 404 | The thing you asked for does not exist, or this workspace can't see it. | Stop using that id. List again to get fresh ids. |
OVERSCOPE_ACK_REQUIRED | 409 | PUT /secrets/{key} or POST /secrets/import: the token can do more than its recipe needs. reason says how. Nothing was saved. | Use a narrower token, or send it again with acknowledge_risk: true. |
VERIFY_FAILED | 422 | The provider said no to a token Syndai checked before saving it. On publish, the app failed content review or the site is suspended. | Fix or replace the token. For publish, read detail. |
PUBLISH_IN_FLIGHT | 409 | POST .../publish: this app is already going live. | Poll GET .../publication until it is done, then publish again if you need to. |
PUBLISH_UNSUPPORTED_REPO | 422 | POST .../publish: only projects Syndai hosts can be published. | Don't retry. A project on GitHub ships through its own pipeline. |
PUBLICATION_NOT_LIVE | 409 | The app isn't live, so this setting or its data can't change now. | Publish the app or wait for it to be live, then try again. |
NO_APP_DATA | 404 | GET .../publication/data.json: the app has no data store. A static site has none. | Don't retry. Only apps with sign-in have data. |
FEATURE_REQUESTS_NEEDS_MEMBERS | 400 | PATCH .../publication set feature_requests to members, but the app has no member sign-in. | Choose public or off. |
EMPTY_UPDATE | 400 | A PATCH sent no field to change. | Send at least one field the route takes. detail lists them. |
BAD_CURSOR | 400 | The cursor was not made by this API. | Start again with no cursor, then page with next_cursor. |
HUMAN_APPROVAL_REQUIRED | 403 | A key asked for something only a person signed in at app.syndai.ai may do. Merge anyway and Go live on a held change are two of these. So are loosening protected files and editing text in a protected file. Nothing changed. | Don't retry with the key. Send the owner to review_url. |
PROTECTED_PATHS_MANAGED_ONLY | 422 | PATCH /repos/{id} set protected_paths on a GitHub project. Protected files work only on projects Syndai hosts. | Don't retry. Use GitHub's own branch rules. |
RUN_IN_FLIGHT | 409 | POST /repos/{id}/text-edits or its undo: a run, or a change going live, holds this site. refusal says which one (RUN_IN_FLIGHT, CHANGE_LANDING or CHANGE_PENDING), and run_id names it. Nothing changed. | Follow run_id with GET /runs/{id}/change until it is done, then edit again. |
TEXT_EDIT_REFUSED | 400/409/422/503 | A text edit or its undo could not swap the text. refusal holds the reason. NOT_LITERAL means code builds the text, and prompt is a ready change request. Others are NOT_EDITABLE, SITE_CHANGED, UNDO_STALE, BUSY, MODERATION_HELD (422), INVALID_TEXT (400), and a site or review that can't be reached (503). Nothing changed. | NOT_LITERAL: start a change with prompt. BUSY, SITE_CHANGED or 503: try again. Anything else: make the change with a run. |
CLI_UPGRADE_REQUIRED | 426 | The syndai CLI that sent this is too old. | Run npx -y syndai@latest. |
PRODUCTION_STEP_UP_REQUIRED | 403 | POST /secrets/resolve asked for production values. No person has allowed this key in the browser in the last 10 minutes. No value was read. | Start POST /cli/production-access. An owner or admin opens its verification_uri_complete and allows it. Poll POST /cli/production-access/token, then try again. The CLI does this for you in a terminal. |
INTERNAL_ERROR | 5xx | Syndai could not finish the request. This is on our side. | Retry with backoff. Contact support if it keeps happening. |
Device login errors
syndai login uses the device login of RFC 8628. Its two routes answer in that shape, not as problem details. Until the login ends, POST /api/v1/cli/device/token returns HTTP 400 with {"error": ...}:
error | what it means | what to do |
|---|---|---|
authorization_pending | no one has said yes or no yet | poll again after interval seconds |
slow_down | you polled faster than interval | wait 5 seconds longer, then poll again |
access_denied | an owner or admin said no | stop. Start a new login if that was a mistake. |
expired_token | no one said yes within 10 minutes, or the key was already handed out | stop and start a new login |
Retries
- Retry
429and5xxwith backoff. Start near one second and double each time. WhenRetry-Afteris there, wait that long first. MISSION_BUSY_RETRYandHITL_REQUEST_CONFLICTare safe to send again after a short wait.POST /runsis safe to retry with the sameexternal_request_id. A repeat never makes a second run. It answersIDEMPOTENCY_REPLAYand points at the first one.- Any other
4xxfails the same way each time. Change the request before you send it again.
The CLI turns these codes into exit codes.