Documentation

Error Handling

Complete reference for OrbitKit API error codes, HTTP status codes, and validation error format.

All API errors return a consistent JSON body:

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description",
    "docUrl": "https://orbitkit.io/api/errors/#error-code",
    "details": []
  }
}

Every error includes a docUrl field linking to the relevant section of this page or the rate limits page.

Error codes

Code HTTP Status Description
UNAUTHORIZED 401 Missing, invalid, or expired authentication token
FORBIDDEN 403 Authenticated but not allowed to perform this action
NOT_FOUND 404 Resource not found (user, app, site, policy, etc.)
VALIDATION_FAILED 400 Request body failed Zod schema validation
RATE_LIMITED 429 Too many requests — slow down
SLUG_TAKEN 409 The requested URL slug is already in use
SLUG_RESERVED 409 The slug is a reserved name (e.g., admin, api)
DOMAIN_IN_USE 409 The domain is already mapped to another app
APP_LIMIT_REACHED 400 Maximum 10 apps per account
API_KEY_LIMIT_REACHED 400 Maximum 10 active API keys per account
SUBSCRIPTION_REQUIRED 403 An active subscription is required to deploy
SUBSCRIPTION_EXISTS 400 This app already has an active subscription
NO_PAYMENT_METHOD 400 No payment method on file
NO_ACTIVE_SUBSCRIPTION 400 No active subscription to cancel or verify
NOT_CANCELING 400 Subscription is not scheduled for cancellation (can’t reactivate)
ALREADY_CANCELING 400 Subscription is already scheduled for cancellation
SUBSCRIPTION_CANCELING 400 Cannot change plan while cancellation is pending (reactivate first)
SAME_PLAN 400 Already on the requested plan
CARD_ERROR 400 Stripe card declined or invalid
PAYMENT_ERROR 400 Generic Stripe payment failure
NO_STRIPE_CUSTOMER 400 User has no Stripe customer record
INVALID_IDEMPOTENCY_KEY 400 Idempotency-Key header exceeds 255 characters
IDEMPOTENCY_KEY_REUSE 422 Same idempotency key used with different request parameters
CERT_CREATION_FAILED 500 SSL certificate provisioning failed
DEPLOY_FAILED 500 Site deployment failed
PREVIEW_FAILED 500 Site preview generation failed
INTERNAL_ERROR 500 Unexpected server error

HTTP status codes

Status Meaning
200 Success
201 Resource created
204 Success, no content (delete operations)
400 Bad request (validation error or invalid state)
401 Unauthorized (missing or invalid token)
403 Forbidden (valid token but insufficient permissions)
404 Not found
409 Conflict (slug taken, domain in use)
429 Rate limited
500 Internal server error

Validation errors

When a request fails Zod schema validation, the details array contains one entry per field:

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Validation failed",
    "details": [
      {
        "field": "appName",
        "message": "Required"
      },
      {
        "field": "slug",
        "message": "String must contain at least 1 character(s)"
      }
    ]
  }
}

Each detail has:

  • field — The field path that failed validation
  • message — What’s wrong with the value

Rate limit headers

When you’re approaching or have exceeded a rate limit:

Header Description
Retry-After Seconds to wait before retrying (on 429 responses)

Handling errors in code

Swift

struct ApiErrorResponse: Decodable {
    struct ApiError: Decodable {
        let code: String
        let message: String
        let details: [ValidationDetail]?
    }
    struct ValidationDetail: Decodable {
        let field: String
        let message: String
    }
    let error: ApiError
}

func handleResponse(_ data: Data, _ response: HTTPURLResponse) throws {
    guard (200...299).contains(response.statusCode) else {
        let apiError = try JSONDecoder().decode(ApiErrorResponse.self, from: data)
        switch apiError.error.code {
        case "UNAUTHORIZED":
            // Refresh token and retry
            break
        case "RATE_LIMITED":
            // Back off and retry
            break
        default:
            throw OrbitKitError.api(apiError.error.code, apiError.error.message)
        }
        return
    }
    // Handle success...
}

JavaScript

async function orbitKitFetch(path, options = {}) {
  const token = await getOrbitKitToken();
  const res = await fetch(`https://api.orbitkit.io/api${path}`, {
    ...options,
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
      ...options.headers,
    },
  });

  if (!res.ok) {
    const { error } = await res.json();
    if (error.code === "UNAUTHORIZED") {
      // Refresh token and retry
    }
    throw new Error(`${error.code}: ${error.message}`);
  }

  return res.status === 204 ? null : res.json();
}