Errors
Handle the HTTP API's closed set of error codes and their structured details.
Every failed operation returns one JSON shape:
1{2 "error": {3 "code": "INVALID_ARGUMENT",4 "message": "Human-readable explanation",5 "details": {}6 }7}code and message are always there. details is optional and holds machine-readable context for that error. Branch on code, not on the message text.
Error codes
| Code | HTTP status | Meaning |
|---|---|---|
INVALID_ARGUMENT | 400 | The JSON or an argument is invalid, a required idempotencyKey is missing, or a confirmation is needed. |
UNAUTHENTICATED | 401 | The bearer credential is missing or invalid. |
FORBIDDEN | 403 | The credential lacks the scope the operation needs, or a signed evidence link is invalid or expired. |
NOT_FOUND | 404 | The resource doesn't exist, or the caller can't reach it. |
CONFLICT | 409 | The change conflicts with what exists, such as an environment name already in use, or an idempotencyKey reused with different arguments. |
LIMIT_REACHED | 402 | Reserved for plan limits. It isn't returned today: see Plan limits. |
UNAVAILABLE | 503 | Autonomy couldn't complete the operation just now. Retry later. |
An unexpected error also uses the code UNAVAILABLE, with the message "The operation could not be completed.", but returns HTTP 500.
Invalid JSON returns 400 INVALID_ARGUMENT with "Invalid JSON body.", and a body that isn't a JSON object returns "The JSON body must be an object." A 401 response also carries the WWW-Authenticate challenge.
Common messages
| Code | Message | details |
|---|---|---|
INVALID_ARGUMENT | idempotencyKey is required for this operation. | field |
INVALID_ARGUMENT | limit must be an integer between 1 and 100. | |
INVALID_ARGUMENT | Give exactly one of testCaseId or testPlanId. | |
INVALID_ARGUMENT | Give either environmentSlug or targets, not both. | |
FORBIDDEN | This operation requires the <scope> scope. | scope |
NOT_FOUND | Project not found. / Test Case not found. / Test Plan not found. / Run not found. / Environment not found. / Issue not found. | The id you sent |
CONFLICT | idempotencyKey was already used with different arguments. | |
CONFLICT | That name is taken in this organization. Pick another. |
A run that can't be triggered also returns INVALID_ARGUMENT, for example when a test plan has no test cases or a test case still has only steps from before v0.5.
Confirmation details
Operations that replace something return a preview instead of writing when confirm: true is missing. For example, test_case.update_steps returns:
1{2 "error": {3 "code": "INVALID_ARGUMENT",4 "message": "Replacing all Test Case steps requires confirm: true.",5 "details": {6 "confirmRequired": true,7 "wouldChange": {8 "stepCountBefore": 4,9 "stepCountAfter": 610 }11 }12 }13}| Operation | Message | wouldChange |
|---|---|---|
test_case.update_steps | Replacing all Test Case steps requires confirm: true. | stepCountBefore, stepCountAfter |
test_plan.set_members | Confirm removing cases from this plan. | removed and added, each [{ testCaseId, name }] |
environment.upsert | Updating an existing environment requires confirm: true. | fields and platforms you supplied |
Nothing is written until you confirm. Read the preview, then send the same request with confirm: true and the same idempotencyKey. A request that returns an error stores nothing under its key, so the key is still free.
Plan limits
When a plan limit stops an operation, such as the run credit on the Free plan, the monthly run count or the number of test cases, the API currently returns UNAVAILABLE with HTTP 500 and the message "The operation could not be completed." It doesn't return LIMIT_REACHED yet. If an operation that worked before starts returning this error, check Organization → Billing. See Billing.
API key rate limits
Organization API keys allow 1,000 requests per hour per key. When a key reaches its limit, the API returns 429 RATE_LIMITED and a Retry-After header with the number of seconds to wait. Invalid, disabled and expired keys still return 401.
This authentication rejection uses a top-level body before the operation runs:
1{2 "code": "RATE_LIMITED",3 "message": "This API key has made too many requests. Try again in 120 seconds."4}Wait for Retry-After before trying again. MCP tools report the same RATE_LIMITED meaning when the underlying API returns this response.
Didn’t find it?