Errors
One actionable error envelope, stable codes, retry guidance, and a request id.
Every error from the typeship API uses the same envelope:
{
"errors": [
{
"type": "request_error",
"code": "invalid_request",
"message": "name is required.",
"retryable": false,
"suggested_action": "Fix the request using the operation schema, then retry.",
"docs_url": "https://typeship.dev/docs/typeship-api/errors"
}
],
"request_id": "req_k3j2h8f0a1b2c3d4"
}errors is always an array, even for a single error. request_id also arrives on every error response as the Request-Id header. Quote it when reporting a problem.
Branch on type, code, and retryable. message is for a person and may be reworded. suggested_action is a stable recovery instruction. Validation errors can also carry field as a JSON Pointer.
Codes
| Code | Status | Retry | When |
|---|---|---|---|
invalid_request | 400 | No | Malformed JSON, a failed field validation, a bad cursor, or an operation that does not apply. The message names the field. |
idempotency_key_reused | 409 | No | The key already identifies a different project-creation request. Reuse the original parameters or send a new key. |
unauthorized | 401 | No | Missing, malformed, expired, or revoked bearer credential. Fix the credential first. |
organization_required | 403 | No | The OAuth grant has no organization context. Reconnect and choose an organization during consent. |
insufficient_scope | 403 | No | The OAuth token lacks the operation's typeship:read, typeship:generate, or typeship:write capability. Reconnect and approve the scope named by WWW-Authenticate, or use an organization API key. |
forbidden | 403 | No | The OAuth token has the capability, but the signed-in organization member is not an admin for an admin-only operation. Ask an organization admin; requesting another scope will not help. |
plan_limit_reached | 402 | No | The Project or hosted feature is outside the account's plan. Free has no Generation-count quota or Target-count restriction; each Target is capped to 25 operations. The response names where to lift the limit. |
not_found | 404 | No | The resource does not exist or belongs to another account. The two are indistinguishable on purpose. |
payload_too_large | 413 | No | A spec over 10MB, inline or fetched. |
spec_error | 422 | No | The document cannot be used as a spec at all. The message is the generator's, quoted verbatim. Warnings never cause this. See Errors and warnings. |
fetch_error | 400 or 502 | 502 only | The spec URL was invalid (400) or could not be fetched (502): unreachable, timed out after 15 seconds, or answered with an error status. |
rate_limited | 429 | After Retry-After | Too many requests. A Retry-After header says how long to wait. See Rate limits. |
internal_error | 500 | Once, with backoff | The generator hit an unexpected condition. Not a problem with your spec. |
Agent guidance
Read errors[0].code, never the message. Honor retryable; when it is false, change the request or ask the user instead of looping. Three cases deserve care:
plan_limit_reachedand generation caps are not failures to retry. A capped generation succeeds with alimitsobject. Report omissions only whenomitted_operationsis above zero. Forreason: "anonymous", offersignup_url. Forreason: "free_plan", useupgrade_urlinstead of asking for another key. A 402 means a stored Project or hosted feature is outside the plan.rate_limitedsays how long. Wait theRetry-Afterseconds once. Anonymous calls also carryRateLimit-LimitandRateLimit-Remainingon every response, so pace before the wall, not after.- Large results are paged and indexed. A generation over the size cap returns
files_omitted: truewith afiles_index; fetch the files you need withGET /v1/generations/{id}/file?path=...instead of asking for the whole generation again. Lists returnhas_moreandnext_cursor.
The typeship CLI turns all of this into its own envelope with stable codes and next_steps (below); the typeship MCP server returns the same bodies as tool errors.
In the typeship SDK
Nothing throws. Every call returns a discriminated result, and the error side is a typed union of the documented errors for that operation. The envelope is the typed body:
const result = await client.projects.create({
name: "Acme API",
definition: { source: { kind: "url", url: "https://api.acme.example.com/openapi.json" } },
targets: [{ name: "Acme TypeScript SDK", generator: "typescript-sdk" }],
});
if (!result.ok) {
result.error.status; // 400
result.error.body.errors[0]; // typed code, retryability, message, and docs URL
result.response?.requestId; // "req_..."
}PaymentRequiredError, NotFoundError, UnprocessableEntityError, and the other documented errors are exported by name. unwrap(result) throws them instead.
See Results and errors for the pattern used by every generated SDK.
In the typeship CLI
Errors print one JSON envelope on stderr and exit 1. The envelope contains status, issues, docs_url, next_steps, and detail.
The API response is available under detail.body. CLI issue codes derive from status and error type, including NO_AUTH, PLAN_LIMIT, RATE_LIMITED, and SPEC_INVALID.
Usage mistakes exit 2 before making a request. See Output and exit codes.