Skip to main content

Errors

Every non-2xx response from the Orunbase API uses a single error envelope. The code is a stable machine-readable identifier — branch on it, not on the message text.

{
"error": {
"code": "validation_failed",
"message": "Request body failed validation",
"details": { "fields": { "name": ["must not be empty"] } },
"requestId": "req_5f2d1c0b9a8e7f6d5c4b3a21"
}
}
  • code — one of the identifiers below.
  • message — human-readable; may change without notice.
  • details — code-specific structured context (may be {}).
  • requestId — quote this in support requests; it ties the response to server-side traces.

Error codes

CodeHTTP statusMeaningRetry guidance
bad_request400Malformed request (bad JSON, invalid parameter)Do not retry unchanged; fix the request
unauthenticated401Missing, invalid, expired, or revoked credentialDo not retry until the credential is fixed
forbidden403Valid credential, insufficient permissionDo not retry; grant the required role
not_found404Resource or route does not exist (or ref did not resolve)Do not retry unchanged
unsupported405 / 415Method not allowed on this route / unsupported media typeDo not retry unchanged
conflict409State conflict (duplicate slug, concurrent modification)Re-read the resource, then decide
precondition_failed412A required precondition on the resource was not metRe-read the resource, then decide
validation_failed422Body parsed but failed field-level validationDo not retry unchanged; see details.fields
423Terraform state backend only: the state lock is held by someone elseTerraform-driven; see below
rate_limited429Token bucket exhausted for a scopeRetry after Retry-After seconds
internal_error500Unexpected server failureRetry with backoff; include requestId if reporting

Unknown codes may appear in the future — treat any unrecognized code as non-retryable by default and fall back on the HTTP status.

The 423 exception

423 is returned only by the Terraform state backend routes, and its body is not the platform envelope: it is the current holder's raw Terraform lock-info document, which Terraform parses directly to print the lock id and owner. This is a documented exception — every other non-2xx response on the API uses the envelope above.

Validation errors

validation_failed carries field-level violations in details.fields — a map of field name to an array of human-readable messages. A malformed Idempotency-Key header also produces validation_failed (with details.header and details.reason) — see Idempotency.

A worked example — creating a project with an empty name:

curl -X POST https://api.orunbase.com/v1/organizations/ws_a1b2c3d4/projects \
-H "Authorization: Bearer $ORUN_CLOUD_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": ""}'
{
"error": {
"code": "validation_failed",
"message": "Request body failed validation",
"details": {
"fields": {
"name": ["must not be empty"]
}
},
"requestId": "req_3e2d1c0b9a8f7e6d5c4b3a21"
}
}

Rate-limit errors

rate_limited (429) includes details: { "scope": "org" | "identity", "retryAfterSeconds": <n> } plus a Retry-After response header. See Rate limits for the scopes and headers.

SDK typed errors

The @saas/sdk client decodes every non-2xx response into a typed error class. All of them extend OrunCloudError, which exposes code, status, requestId, details, the raw envelope, and (when available) the original response. Unknown codes decode to the base class, so instanceof OrunCloudError always matches.

CodeSDK classExtra fields
bad_requestBadRequestError
unauthenticatedUnauthenticatedError
forbiddenForbiddenError
not_foundNotFoundError
conflictConflictError
precondition_failedPreconditionFailedError
validation_failedValidationErrorfields: Record<string, string[]>
unsupportedUnsupportedError
rate_limitedRateLimitErrorretryAfterSeconds, scope, windows (+ orgWindow / identityWindow accessors)
internal_errorInternalError
import { OrunCloud, ValidationError, RateLimitError } from "@saas/sdk";

try {
await client.projects.create("org_1f6a3c9e", { name: "" });
} catch (err) {
if (err instanceof ValidationError) {
console.error(err.fields); // { name: ["must not be empty"] }
} else if (err instanceof RateLimitError) {
await sleep((err.retryAfterSeconds ?? 1) * 1000);
} else {
throw err; // includes err.requestId for support
}
}
note

The SDK has no built-in retries — retry policy is yours. RateLimitError.retryAfterSeconds and the code table above tell you what is safe to retry.