Errors
The Polimorf error envelope, HTTP status mapping, and the typed SDK error classes you can branch on.
Every error the API returns uses one stable envelope: a machine-readable
code, the HTTP status, a human message, and — for validation errors — a
list of offending fields.
The error envelope
{
"code": "VALIDATION_ERROR",
"status": 400,
"message": "messages must contain at least one message",
"fields": [{ "field": "messages", "issue": "must not be empty" }]
}codeis the value to branch on in your application. It is drawn from a closed catalog and is stable across releases.messageis for humans (logs, debugging) — do not match on it.fieldsis present only forVALIDATION_ERROR(400).
Status mapping
The SDK throws the most specific PolimorfApiError subclass for each status, so
you can catch with instanceof:
| Status | SDK class | Typical codes |
|---|---|---|
400 | BadRequestError | VALIDATION_ERROR, INVALID_REQUEST |
401 | AuthenticationError | UNAUTHENTICATED, INVALID_CREDENTIALS |
403 | PermissionDeniedError | FORBIDDEN |
404 | NotFoundError | ASSISTANT_NOT_FOUND, DEPLOYMENT_NOT_FOUND |
409 | ConflictError | IDEMPOTENCY_KEY_CONFLICT |
429 | RateLimitError | RATE_LIMITED, COST_QUOTA_EXCEEDED |
≥500 | ServerError | INTERNAL_ERROR |
An unmapped status is thrown as the base PolimorfApiError, so an unforeseen
status never produces an unexpected shape.
Handling errors
import {
createClient,
PolimorfApiError,
PolimorfError,
BadRequestError,
RateLimitError,
} from '@polimorfapp/sdk';
const client = createClient();
try {
await client.assistant('support').run({ input: 'Hello' });
} catch (err) {
if (err instanceof BadRequestError) {
// Inspect err.fields for the offending inputs.
console.error(err.fields);
} else if (err instanceof RateLimitError) {
// 429 — back off and retry.
} else if (err instanceof PolimorfApiError) {
// Any other non-2xx: err.status and err.code are set.
console.error(err.status, err.code, err.message);
} else if (err instanceof PolimorfError) {
// Transport failure: network, timeout, or abort.
console.error('transport error', err.cause);
} else {
throw err;
}
}Two error families exist. PolimorfApiError means the server replied with
a non-2xx envelope (it has status, code, and sometimes fields). Its base,
PolimorfError, is thrown when the request never completed — a network
failure, timeout, or abort. Catch PolimorfError to cover both.
Switching on the code
For finer control than status classes, switch on the stable code. Unknown
codes stay typed as string, so a newly published server code never breaks an
older SDK build:
if (err instanceof PolimorfApiError) {
switch (err.code) {
case 'COST_QUOTA_EXCEEDED':
// Workspace spend limit reached.
break;
case 'INVALID_CREDENTIALS':
// Bad or revoked API key.
break;
default:
// Handle or rethrow.
}
}Error code catalog
The codes most relevant to the runtime API:
| Code | Meaning |
|---|---|
VALIDATION_ERROR | Request body failed validation; see fields. |
INVALID_REQUEST | Request was malformed or semantically invalid. |
PAYLOAD_TOO_LARGE | Request body exceeded the size limit. |
UNAUTHENTICATED | Missing or malformed credentials. |
INVALID_CREDENTIALS | Unknown or revoked API key. |
FORBIDDEN | Key lacks the required scope. |
RATE_LIMITED | Too many requests; retry after backoff. |
COST_QUOTA_EXCEEDED | Workspace spend quota reached. |
ASSISTANT_NOT_FOUND | No assistant matches the slug. |
DEPLOYMENT_NOT_FOUND | The assistant is not deployed to the target environment. |
IDEMPOTENCY_KEY_CONFLICT | An idempotency key was reused with a different body. |
INTERNAL_ERROR | Unexpected server error; safe to retry. |
The catalog is a closed set; the SDK vendors it as the ErrorCode union for
autocomplete while still accepting any string for forward compatibility.