typeship APIAPI

Errors

One error envelope, a fixed set of codes, and a request id on every response.

Every error from the typeship API uses the same envelope:

{
  "errors": [
    { "code": "invalid_request", "message": "name is required." }
  ],
  "request_id": "req_k3j2h8f0a1b2c3d4"
}

errors is always an array, even for a single error. request_id also arrives on every response as the x-request-id header. Quote it when reporting a problem.

Codes

CodeStatusRetryWhen
invalid_request400NoMalformed JSON, a failed field validation, a bad cursor, or an operation that does not apply (regenerating a repository-sourced project on demand). The message names the field.
unauthorized401NoMissing, malformed, or revoked API key. Fix the credential first.
plan_limit_reached402NoThe account has used its included hosted generations. The response names where to lift it.
not_found404NoThe resource does not exist or belongs to another account. The two are indistinguishable on purpose.
payload_too_large413NoA spec over 10MB, inline or fetched.
spec_error422NoThe 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_error400 or 502502 onlyThe spec URL was invalid (400) or could not be fetched (502): unreachable, timed out after 15 seconds, or answered with an error status.
rate_limited429After Retry-AfterToo many requests. A Retry-After header says how long to wait. See Rate limits.
internal_error500Once, with backoffThe generator hit an unexpected condition. Not a problem with your spec.

Agent guidance

Read errors[0].code, never the message, and act on the table above. Three cases deserve care:

  • plan_limit_reached and anonymous caps are not failures to retry. An anonymous POST /v1/generate succeeds with a limits object (max_operations, omitted_operations, reason, signup_url, upgrade_url); a 402 carries the same idea for hosted generation. Tell the user what was left out and where the cap lifts; do not loop on the same call.
  • rate_limited says how long. Wait the Retry-After seconds once. Anonymous calls also carry X-RateLimit-Limit and X-RateLimit-Remaining on every response, so pace before the wall, not after.
  • Large results are paged and indexed. A generation over the size cap returns files_omitted: true with a files_index; fetch the files you need with GET /v1/generations/{id}/file?path=... instead of asking for the whole generation again. Lists return has_more and next_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: "" });
if (!result.ok) {
  result.error.status;          // 400
  result.error.body.errors[0];  // { code: "invalid_request", message: "name can't be empty." }
  result.response?.requestId;   // "req_..."
}

PaymentRequiredError, NotFoundError, UnprocessableEntityError, and the rest are exported by name. unwrap(result) throws them instead. See Results and errors for the pattern, which is the same one every SDK typeship generates uses.

In the typeship CLI

Errors print one JSON envelope on stderr, {status, issues: [{code, message}], docs_url, next_steps, detail}, and exit 1; the API's envelope above rides along as detail.body, and the typeship CLI's issues[].code is derived from the status (NO_AUTH, PLAN_LIMIT, RATE_LIMITED, SPEC_INVALID, and so on). Usage mistakes exit 2 before any request is made. See Output and exit codes.

On this page