@aexhq/sdk 0.40.4 → 0.40.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +159 -159
- package/dist/_contracts/api-key.js +0 -1
- package/dist/_contracts/bundle-manifest.js +0 -1
- package/dist/_contracts/connection-ticket.js +0 -1
- package/dist/_contracts/error-codes.js +0 -1
- package/dist/_contracts/error-factory.js +0 -1
- package/dist/_contracts/event-envelope.js +0 -1
- package/dist/_contracts/event-stream-client.js +0 -1
- package/dist/_contracts/event-view.js +0 -1
- package/dist/_contracts/http.js +0 -1
- package/dist/_contracts/index.js +0 -1
- package/dist/_contracts/internal.d.ts +36 -0
- package/dist/_contracts/internal.js +155 -6
- package/dist/_contracts/models.js +0 -1
- package/dist/_contracts/operations.js +1 -2
- package/dist/_contracts/post-hook.js +0 -1
- package/dist/_contracts/provider-support.js +0 -1
- package/dist/_contracts/run-artifacts.js +0 -1
- package/dist/_contracts/run-config.js +0 -1
- package/dist/_contracts/run-cost.js +0 -1
- package/dist/_contracts/run-custody.js +0 -1
- package/dist/_contracts/run-record.js +0 -1
- package/dist/_contracts/run-retention.js +0 -1
- package/dist/_contracts/run-trace.js +0 -1
- package/dist/_contracts/run-unit.js +0 -1
- package/dist/_contracts/runner-event.js +0 -1
- package/dist/_contracts/runtime-manifest.js +0 -1
- package/dist/_contracts/runtime-security-profile.js +0 -1
- package/dist/_contracts/runtime-sizes.js +0 -1
- package/dist/_contracts/runtime-types.js +0 -1
- package/dist/_contracts/sdk-errors.js +0 -1
- package/dist/_contracts/sdk-secrets.js +0 -1
- package/dist/_contracts/side-effect-audit.js +0 -1
- package/dist/_contracts/sse.js +0 -0
- package/dist/_contracts/stable.js +0 -1
- package/dist/_contracts/status.js +0 -1
- package/dist/_contracts/submission.js +0 -1
- package/dist/_contracts/suggest.js +0 -1
- package/dist/_contracts/webhook-verify.js +0 -1
- package/dist/asset-upload.d.ts +3 -43
- package/dist/asset-upload.js +1 -109
- package/dist/asset-upload.js.map +1 -1
- package/dist/cli.mjs +354 -781
- package/dist/cli.mjs.sha256 +1 -1
- package/dist/fetch-archive.js +76 -16
- package/dist/fetch-archive.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/authentication.md +125 -125
- package/docs/billing.md +118 -118
- package/docs/concepts/agent-tools.md +66 -66
- package/docs/concepts/composition.md +37 -37
- package/docs/concepts/providers-and-runtimes.md +54 -54
- package/docs/concepts/runs.md +49 -49
- package/docs/concepts/subagents.md +88 -88
- package/docs/credentials.md +125 -125
- package/docs/defaults.md +64 -64
- package/docs/errors.md +196 -196
- package/docs/events.md +243 -243
- package/docs/limits-and-quotas.md +144 -144
- package/docs/limits.md +50 -50
- package/docs/mcp.md +44 -44
- package/docs/networking.md +163 -163
- package/docs/outputs.md +267 -267
- package/docs/public-surface.json +77 -77
- package/docs/quickstart.md +119 -119
- package/docs/release.md +38 -38
- package/docs/retries.md +129 -129
- package/docs/run-config.md +58 -58
- package/docs/run-record.md +55 -55
- package/docs/secrets.md +125 -130
- package/docs/testing.md +35 -35
- package/docs/vision-skills.md +91 -91
- package/docs/webhooks.md +127 -131
- package/examples/feature-tour.ts +282 -282
- package/examples/spike-settle-latency.ts +125 -125
- package/package.json +1 -1
package/docs/errors.md
CHANGED
|
@@ -1,196 +1,196 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Errors
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Errors
|
|
6
|
-
|
|
7
|
-
Every API error is a JSON body with a machine-readable `error` code; most also
|
|
8
|
-
carry a human `message` and the self-describing fields named below.
|
|
9
|
-
|
|
10
|
-
## Typed errors in the SDK
|
|
11
|
-
|
|
12
|
-
The SDK maps every non-2xx response through one factory to a typed exception. All
|
|
13
|
-
inherit `AexApiError`, which carries the HTTP `status`, the redacted parsed
|
|
14
|
-
`body`, the server's stable `apiCode` (a machine-branchable identity distinct
|
|
15
|
-
from the human `message`), and a `requestId` for support correlation. The
|
|
16
|
-
factory dispatches to a subclass by code/status:
|
|
17
|
-
|
|
18
|
-
| Class | Fires for | Extra fields |
|
|
19
|
-
| --- | --- | --- |
|
|
20
|
-
| `AexAuthError` | `401` / `403` (unauthorized, forbidden, insufficient_scope, token_invalid/revoked/expired, malformed_token) | `requiredScope` (on `insufficient_scope`) |
|
|
21
|
-
| `AexIdempotencyConflictError` | `409` `idempotency_conflict` | — |
|
|
22
|
-
| `AexNotFoundError` | `404` `not_found` | — |
|
|
23
|
-
| `AexRateLimitError` | `429` (rate_limited, workspace_concurrency_exceeded, workspace_submit_rate_exceeded) | `retryAfterMs` (when advertised) |
|
|
24
|
-
| `AexApiError` (base) | every other stable code (session_busy, run_not_terminal, insufficient_balance, workspace_spend_cap_exceeded, upstream_error, internal_error, …) | — |
|
|
25
|
-
|
|
26
|
-
Branch with the exported guards instead of parsing bodies or matching status
|
|
27
|
-
codes: `isAuthError`, `isInsufficientScope`, `isIdempotencyConflict`,
|
|
28
|
-
`isNotFound`, and `isRateLimited`.
|
|
29
|
-
|
|
30
|
-
```ts
|
|
31
|
-
import { isInsufficientScope, isIdempotencyConflict } from "@aexhq/sdk";
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
await aex.run(config);
|
|
35
|
-
} catch (err) {
|
|
36
|
-
if (isInsufficientScope(err)) {
|
|
37
|
-
// err.requiredScope names the missing scope; mint a key that includes it.
|
|
38
|
-
} else if (isIdempotencyConflict(err)) {
|
|
39
|
-
// same key, different body — use a fresh key or resend the byte-identical body.
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
The **stable** `apiCode` set the SDK types and dispatches on is: `unauthorized`,
|
|
45
|
-
`forbidden`, `insufficient_scope`, `token_invalid`, `token_revoked`,
|
|
46
|
-
`token_expired`, `malformed_token`, `not_found`, `idempotency_conflict`,
|
|
47
|
-
`session_busy`, `run_not_terminal`, `unknown_workspace`,
|
|
48
|
-
`workspace_concurrency_exceeded`, `workspace_submit_rate_exceeded`,
|
|
49
|
-
`workspace_spend_cap_exceeded`, `insufficient_balance`, `rate_limited`,
|
|
50
|
-
`upstream_error`, `internal_error`. A code outside this set (e.g. a validation
|
|
51
|
-
`error` a route reports) still surfaces as an `AexApiError` with the `status` and
|
|
52
|
-
`body.error` preserved; `apiCode` is then `undefined`.
|
|
53
|
-
|
|
54
|
-
Transport failures with no HTTP response (DNS, connection refused, TLS reset)
|
|
55
|
-
surface as `AexNetworkError`; client-side config validation surfaces as
|
|
56
|
-
`RunConfigValidationError` (`err.code === "RUN_CONFIG_INVALID"`) before any
|
|
57
|
-
request is sent.
|
|
58
|
-
|
|
59
|
-
## 401 — authentication
|
|
60
|
-
|
|
61
|
-
| Code | Meaning |
|
|
62
|
-
| --- | --- |
|
|
63
|
-
| `unauthorized` | Missing or empty bearer token. |
|
|
64
|
-
| `token_invalid` | Bearer token is structurally valid but does not match a live credential, has a bad signature, or belongs to an inactive workspace. |
|
|
65
|
-
| `token_revoked` | Bearer token matched a revoked credential. |
|
|
66
|
-
| `token_expired` | Short-lived internal writer token is past its expiry. |
|
|
67
|
-
|
|
68
|
-
Check the token value and that it has not been deleted or revoked. `aex whoami`
|
|
69
|
-
is the cheapest way to validate a credential. See
|
|
70
|
-
[Authentication](authentication.md).
|
|
71
|
-
|
|
72
|
-
## 403 — authorization
|
|
73
|
-
|
|
74
|
-
| Code | Meaning |
|
|
75
|
-
| --- | --- |
|
|
76
|
-
| `insufficient_scope` | The token is valid but lacks the route's required scope. The body's `requiredScope` field names the missing scope. |
|
|
77
|
-
| `unknown_workspace` | The token does not route to a known workspace. |
|
|
78
|
-
| `forbidden` | The authenticated workspace does not own the addressed resource. |
|
|
79
|
-
|
|
80
|
-
```json
|
|
81
|
-
{ "error": "insufficient_scope", "requiredScope": "runs:write" }
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## 400 — validation
|
|
85
|
-
|
|
86
|
-
| Code | Meaning |
|
|
87
|
-
| --- | --- |
|
|
88
|
-
| `bad_request` | Missing or unparseable request body. |
|
|
89
|
-
| `invalid_submission` | The submission failed shape validation; `message` names the offending field. |
|
|
90
|
-
| `missing_provider_key` | The submission names a provider but carries no BYOK key for it (`apiKeys[provider]`). |
|
|
91
|
-
| `malformed_token` | The bearer value is not a structurally valid aex token. |
|
|
92
|
-
|
|
93
|
-
400s are permanent for that request — fix the input rather than retrying.
|
|
94
|
-
The SDK's client-side validation (`RunConfigValidationError`) catches most of
|
|
95
|
-
these before the request is sent.
|
|
96
|
-
|
|
97
|
-
## 402 — payment required
|
|
98
|
-
|
|
99
|
-
Two distinct submit gates return 402; both bodies are self-describing.
|
|
100
|
-
|
|
101
|
-
**`insufficient_balance`** — the workspace prepaid balance is at or below the
|
|
102
|
-
effective submit floor. Top up the balance or bind a payment method.
|
|
103
|
-
|
|
104
|
-
```json
|
|
105
|
-
{
|
|
106
|
-
"error": "insufficient_balance",
|
|
107
|
-
"message": "Workspace balance is depleted; top up your prepaid balance or bind a payment method to submit runs.",
|
|
108
|
-
"balanceUsd": 0,
|
|
109
|
-
"balanceGraceFloorUsd": 0,
|
|
110
|
-
"paymentMethodStatus": "none",
|
|
111
|
-
"planKey": "default"
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
`balanceGraceFloorUsd` is the payment-method-aware floor the gate compared
|
|
116
|
-
against (`paymentMethodStatus: "active"` folds a bounded card overdraft into
|
|
117
|
-
it, so the floor can be negative).
|
|
118
|
-
|
|
119
|
-
**`workspace_spend_cap_exceeded`** — the workspace's monthly spend cap is
|
|
120
|
-
reached. The cap resets at the start of the next UTC month; contact support to
|
|
121
|
-
raise it.
|
|
122
|
-
|
|
123
|
-
```json
|
|
124
|
-
{
|
|
125
|
-
"error": "workspace_spend_cap_exceeded",
|
|
126
|
-
"message": "Monthly spend cap of $250 reached ($251.13 accrued this month). The cap resets at the start of the next UTC month; contact support to raise it.",
|
|
127
|
-
"capUsd": 250,
|
|
128
|
-
"accruedUsd": 251.13
|
|
129
|
-
}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## 429 — rate limits
|
|
133
|
-
|
|
134
|
-
**`workspace_concurrency_exceeded`** — admitting one more live run would exceed
|
|
135
|
-
the workspace's concurrent-run cap. Wait for a run to finish, or contact
|
|
136
|
-
support to raise the cap.
|
|
137
|
-
|
|
138
|
-
```json
|
|
139
|
-
{
|
|
140
|
-
"error": "workspace_concurrency_exceeded",
|
|
141
|
-
"message": "Workspace concurrency limit reached: 50 live runs at the cap of 50. Wait for a run to finish, or contact support to raise your workspace limit.",
|
|
142
|
-
"cap": 50,
|
|
143
|
-
"observed": 50
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
**`workspace_submit_rate_exceeded`** — too many submits in the current
|
|
148
|
-
one-minute window. Retry shortly.
|
|
149
|
-
|
|
150
|
-
```json
|
|
151
|
-
{
|
|
152
|
-
"error": "workspace_submit_rate_exceeded",
|
|
153
|
-
"message": "Submit rate limit of 120/minute exceeded. Retry shortly, or contact support to raise your workspace limit.",
|
|
154
|
-
"perMin": 120,
|
|
155
|
-
"observed": 121
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
The `limit`-naming fields (`cap`/`perMin`) and the `observed` window value make
|
|
160
|
-
each deny self-describing, so a client can back off proportionally. To
|
|
161
|
-
anticipate both 429s and both 402s *before* submitting, read the effective caps
|
|
162
|
-
from `aex.whoami().limits` — the values come from the same resolution code the
|
|
163
|
-
gates enforce. See [Limits & quotas](limits-and-quotas.md).
|
|
164
|
-
|
|
165
|
-
## 404 — not found
|
|
166
|
-
|
|
167
|
-
`not_found`: the id does not exist **or** belongs to another workspace (aex
|
|
168
|
-
does not distinguish the two). The SDK raises `AexNotFoundError` (guard:
|
|
169
|
-
`isNotFound(err)`).
|
|
170
|
-
|
|
171
|
-
## 409 — idempotency conflict
|
|
172
|
-
|
|
173
|
-
`idempotency_conflict`: the `idempotencyKey` was already used with a **different**
|
|
174
|
-
request body. The SDK raises `AexIdempotencyConflictError` (guard:
|
|
175
|
-
`isIdempotencyConflict(err)`).
|
|
176
|
-
|
|
177
|
-
**Remedy:** use a fresh idempotency key for a genuinely new request, or resubmit
|
|
178
|
-
the byte-identical body to replay the original result (a matching retry returns
|
|
179
|
-
the existing session rather than conflicting). Note that the SDK validates the
|
|
180
|
-
key client-side first: an empty or whitespace-only `idempotencyKey` throws
|
|
181
|
-
`RunConfigValidationError` before the request is sent — never pass `""`.
|
|
182
|
-
|
|
183
|
-
## 5xx — server errors
|
|
184
|
-
|
|
185
|
-
| Code | Meaning |
|
|
186
|
-
| --- | --- |
|
|
187
|
-
| `internal_error` (500) | Unexpected server fault. Retry with backoff; report persistent cases. |
|
|
188
|
-
| `db_resuming` (503) | The database tier is resuming from idle. Transient — retry. |
|
|
189
|
-
|
|
190
|
-
The SDK retries transient failures automatically: HTTP `429`, `5xx`, `529`, and
|
|
191
|
-
network errors get bounded exponential backoff with full jitter, honoring any
|
|
192
|
-
`Retry-After` header. Tune or disable this with the client `retry` option; use
|
|
193
|
-
`isRateLimited(err)` / `AexRateLimitError` to handle persistent throttling
|
|
194
|
-
without parsing raw bodies. Idempotent submit retries are safe — the SDK
|
|
195
|
-
attaches a stable idempotency key to billable session create/send requests, so
|
|
196
|
-
a retried request never double-submits.
|
|
1
|
+
---
|
|
2
|
+
title: Errors
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Errors
|
|
6
|
+
|
|
7
|
+
Every API error is a JSON body with a machine-readable `error` code; most also
|
|
8
|
+
carry a human `message` and the self-describing fields named below.
|
|
9
|
+
|
|
10
|
+
## Typed errors in the SDK
|
|
11
|
+
|
|
12
|
+
The SDK maps every non-2xx response through one factory to a typed exception. All
|
|
13
|
+
inherit `AexApiError`, which carries the HTTP `status`, the redacted parsed
|
|
14
|
+
`body`, the server's stable `apiCode` (a machine-branchable identity distinct
|
|
15
|
+
from the human `message`), and a `requestId` for support correlation. The
|
|
16
|
+
factory dispatches to a subclass by code/status:
|
|
17
|
+
|
|
18
|
+
| Class | Fires for | Extra fields |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| `AexAuthError` | `401` / `403` (unauthorized, forbidden, insufficient_scope, token_invalid/revoked/expired, malformed_token) | `requiredScope` (on `insufficient_scope`) |
|
|
21
|
+
| `AexIdempotencyConflictError` | `409` `idempotency_conflict` | — |
|
|
22
|
+
| `AexNotFoundError` | `404` `not_found` | — |
|
|
23
|
+
| `AexRateLimitError` | `429` (rate_limited, workspace_concurrency_exceeded, workspace_submit_rate_exceeded) | `retryAfterMs` (when advertised) |
|
|
24
|
+
| `AexApiError` (base) | every other stable code (session_busy, run_not_terminal, insufficient_balance, workspace_spend_cap_exceeded, upstream_error, internal_error, …) | — |
|
|
25
|
+
|
|
26
|
+
Branch with the exported guards instead of parsing bodies or matching status
|
|
27
|
+
codes: `isAuthError`, `isInsufficientScope`, `isIdempotencyConflict`,
|
|
28
|
+
`isNotFound`, and `isRateLimited`.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { isInsufficientScope, isIdempotencyConflict } from "@aexhq/sdk";
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await aex.run(config);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (isInsufficientScope(err)) {
|
|
37
|
+
// err.requiredScope names the missing scope; mint a key that includes it.
|
|
38
|
+
} else if (isIdempotencyConflict(err)) {
|
|
39
|
+
// same key, different body — use a fresh key or resend the byte-identical body.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The **stable** `apiCode` set the SDK types and dispatches on is: `unauthorized`,
|
|
45
|
+
`forbidden`, `insufficient_scope`, `token_invalid`, `token_revoked`,
|
|
46
|
+
`token_expired`, `malformed_token`, `not_found`, `idempotency_conflict`,
|
|
47
|
+
`session_busy`, `run_not_terminal`, `unknown_workspace`,
|
|
48
|
+
`workspace_concurrency_exceeded`, `workspace_submit_rate_exceeded`,
|
|
49
|
+
`workspace_spend_cap_exceeded`, `insufficient_balance`, `rate_limited`,
|
|
50
|
+
`upstream_error`, `internal_error`. A code outside this set (e.g. a validation
|
|
51
|
+
`error` a route reports) still surfaces as an `AexApiError` with the `status` and
|
|
52
|
+
`body.error` preserved; `apiCode` is then `undefined`.
|
|
53
|
+
|
|
54
|
+
Transport failures with no HTTP response (DNS, connection refused, TLS reset)
|
|
55
|
+
surface as `AexNetworkError`; client-side config validation surfaces as
|
|
56
|
+
`RunConfigValidationError` (`err.code === "RUN_CONFIG_INVALID"`) before any
|
|
57
|
+
request is sent.
|
|
58
|
+
|
|
59
|
+
## 401 — authentication
|
|
60
|
+
|
|
61
|
+
| Code | Meaning |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `unauthorized` | Missing or empty bearer token. |
|
|
64
|
+
| `token_invalid` | Bearer token is structurally valid but does not match a live credential, has a bad signature, or belongs to an inactive workspace. |
|
|
65
|
+
| `token_revoked` | Bearer token matched a revoked credential. |
|
|
66
|
+
| `token_expired` | Short-lived internal writer token is past its expiry. |
|
|
67
|
+
|
|
68
|
+
Check the token value and that it has not been deleted or revoked. `aex whoami`
|
|
69
|
+
is the cheapest way to validate a credential. See
|
|
70
|
+
[Authentication](authentication.md).
|
|
71
|
+
|
|
72
|
+
## 403 — authorization
|
|
73
|
+
|
|
74
|
+
| Code | Meaning |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| `insufficient_scope` | The token is valid but lacks the route's required scope. The body's `requiredScope` field names the missing scope. |
|
|
77
|
+
| `unknown_workspace` | The token does not route to a known workspace. |
|
|
78
|
+
| `forbidden` | The authenticated workspace does not own the addressed resource. |
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{ "error": "insufficient_scope", "requiredScope": "runs:write" }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 400 — validation
|
|
85
|
+
|
|
86
|
+
| Code | Meaning |
|
|
87
|
+
| --- | --- |
|
|
88
|
+
| `bad_request` | Missing or unparseable request body. |
|
|
89
|
+
| `invalid_submission` | The submission failed shape validation; `message` names the offending field. |
|
|
90
|
+
| `missing_provider_key` | The submission names a provider but carries no BYOK key for it (`apiKeys[provider]`). |
|
|
91
|
+
| `malformed_token` | The bearer value is not a structurally valid aex token. |
|
|
92
|
+
|
|
93
|
+
400s are permanent for that request — fix the input rather than retrying.
|
|
94
|
+
The SDK's client-side validation (`RunConfigValidationError`) catches most of
|
|
95
|
+
these before the request is sent.
|
|
96
|
+
|
|
97
|
+
## 402 — payment required
|
|
98
|
+
|
|
99
|
+
Two distinct submit gates return 402; both bodies are self-describing.
|
|
100
|
+
|
|
101
|
+
**`insufficient_balance`** — the workspace prepaid balance is at or below the
|
|
102
|
+
effective submit floor. Top up the balance or bind a payment method.
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"error": "insufficient_balance",
|
|
107
|
+
"message": "Workspace balance is depleted; top up your prepaid balance or bind a payment method to submit runs.",
|
|
108
|
+
"balanceUsd": 0,
|
|
109
|
+
"balanceGraceFloorUsd": 0,
|
|
110
|
+
"paymentMethodStatus": "none",
|
|
111
|
+
"planKey": "default"
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`balanceGraceFloorUsd` is the payment-method-aware floor the gate compared
|
|
116
|
+
against (`paymentMethodStatus: "active"` folds a bounded card overdraft into
|
|
117
|
+
it, so the floor can be negative).
|
|
118
|
+
|
|
119
|
+
**`workspace_spend_cap_exceeded`** — the workspace's monthly spend cap is
|
|
120
|
+
reached. The cap resets at the start of the next UTC month; contact support to
|
|
121
|
+
raise it.
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"error": "workspace_spend_cap_exceeded",
|
|
126
|
+
"message": "Monthly spend cap of $250 reached ($251.13 accrued this month). The cap resets at the start of the next UTC month; contact support to raise it.",
|
|
127
|
+
"capUsd": 250,
|
|
128
|
+
"accruedUsd": 251.13
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## 429 — rate limits
|
|
133
|
+
|
|
134
|
+
**`workspace_concurrency_exceeded`** — admitting one more live run would exceed
|
|
135
|
+
the workspace's concurrent-run cap. Wait for a run to finish, or contact
|
|
136
|
+
support to raise the cap.
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"error": "workspace_concurrency_exceeded",
|
|
141
|
+
"message": "Workspace concurrency limit reached: 50 live runs at the cap of 50. Wait for a run to finish, or contact support to raise your workspace limit.",
|
|
142
|
+
"cap": 50,
|
|
143
|
+
"observed": 50
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**`workspace_submit_rate_exceeded`** — too many submits in the current
|
|
148
|
+
one-minute window. Retry shortly.
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"error": "workspace_submit_rate_exceeded",
|
|
153
|
+
"message": "Submit rate limit of 120/minute exceeded. Retry shortly, or contact support to raise your workspace limit.",
|
|
154
|
+
"perMin": 120,
|
|
155
|
+
"observed": 121
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The `limit`-naming fields (`cap`/`perMin`) and the `observed` window value make
|
|
160
|
+
each deny self-describing, so a client can back off proportionally. To
|
|
161
|
+
anticipate both 429s and both 402s *before* submitting, read the effective caps
|
|
162
|
+
from `aex.whoami().limits` — the values come from the same resolution code the
|
|
163
|
+
gates enforce. See [Limits & quotas](limits-and-quotas.md).
|
|
164
|
+
|
|
165
|
+
## 404 — not found
|
|
166
|
+
|
|
167
|
+
`not_found`: the id does not exist **or** belongs to another workspace (aex
|
|
168
|
+
does not distinguish the two). The SDK raises `AexNotFoundError` (guard:
|
|
169
|
+
`isNotFound(err)`).
|
|
170
|
+
|
|
171
|
+
## 409 — idempotency conflict
|
|
172
|
+
|
|
173
|
+
`idempotency_conflict`: the `idempotencyKey` was already used with a **different**
|
|
174
|
+
request body. The SDK raises `AexIdempotencyConflictError` (guard:
|
|
175
|
+
`isIdempotencyConflict(err)`).
|
|
176
|
+
|
|
177
|
+
**Remedy:** use a fresh idempotency key for a genuinely new request, or resubmit
|
|
178
|
+
the byte-identical body to replay the original result (a matching retry returns
|
|
179
|
+
the existing session rather than conflicting). Note that the SDK validates the
|
|
180
|
+
key client-side first: an empty or whitespace-only `idempotencyKey` throws
|
|
181
|
+
`RunConfigValidationError` before the request is sent — never pass `""`.
|
|
182
|
+
|
|
183
|
+
## 5xx — server errors
|
|
184
|
+
|
|
185
|
+
| Code | Meaning |
|
|
186
|
+
| --- | --- |
|
|
187
|
+
| `internal_error` (500) | Unexpected server fault. Retry with backoff; report persistent cases. |
|
|
188
|
+
| `db_resuming` (503) | The database tier is resuming from idle. Transient — retry. |
|
|
189
|
+
|
|
190
|
+
The SDK retries transient failures automatically: HTTP `429`, `5xx`, `529`, and
|
|
191
|
+
network errors get bounded exponential backoff with full jitter, honoring any
|
|
192
|
+
`Retry-After` header. Tune or disable this with the client `retry` option; use
|
|
193
|
+
`isRateLimited(err)` / `AexRateLimitError` to handle persistent throttling
|
|
194
|
+
without parsing raw bodies. Idempotent submit retries are safe — the SDK
|
|
195
|
+
attaches a stable idempotency key to billable session create/send requests, so
|
|
196
|
+
a retried request never double-submits.
|