Errors

The Zivvy API uses standard HTTP status codes and returns a consistent error response body. When a request fails, the response includes an error object with a machine-readable code, a human-readable message, and the HTTP status.

Error response format

Every error response follows this structure:

json{
  "error": {
    "code": "NOT_FOUND",
    "message": "Item IT-99999 does not exist.",
    "status": 404
  }
}
FieldTypeDescription
codestringA machine-readable error code (e.g., NOT_FOUND, VALIDATION_ERROR)
messagestringA human-readable description of what went wrong
statusintegerThe HTTP status code

Some errors include additional fields:

json{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed.",
    "status": 422,
    "details": [
      {
        "field": "item_name",
        "message": "item_name is required."
      },
      {
        "field": "standard_rate",
        "message": "standard_rate must be a positive number."
      }
    ]
  }
}

Error codes

401 Unauthorized — UNAUTHORIZED

The request is missing an API key, or the key is invalid or revoked.

json{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or missing API key.",
    "status": 401
  }
}

Common causes:

  • No X-API-Key header on the request
  • The API key has been revoked
  • The key has a typo or is from a different environment
  • Using a test key (zk_test_) against production, or vice versa

Fix: Verify your API key in the dashboard and ensure the X-API-Key header is set correctly.

403 Forbidden — FORBIDDEN

The API key is valid but does not have permission for this action.

json{
  "error": {
    "code": "FORBIDDEN",
    "message": "API key does not have write permission.",
    "status": 403
  }
}

Common causes:

  • Using a read-scoped key for a write operation (POST, PUT, DELETE)
  • Using a write-scoped key for admin-only actions (deleting resources, managing keys)
  • The key's IP allowlist does not include the requesting IP (Business plan)

Fix: Check the key's scope in the dashboard. Create a new key with the appropriate scope if needed.

404 Not Found — NOT_FOUND

The requested resource does not exist.

json{
  "error": {
    "code": "NOT_FOUND",
    "message": "Item IT-99999 does not exist.",
    "status": 404
  }
}

Common causes:

  • The resource ID is incorrect
  • The resource was deleted
  • A typo in the endpoint URL

Fix: Verify the resource ID. Use the list endpoint to find valid IDs.

422 Unprocessable Entity — VALIDATION_ERROR

The request body or query parameters failed validation.

json{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed.",
    "status": 422,
    "details": [
      {
        "field": "email",
        "message": "email must be a valid email address."
      }
    ]
  }
}

Common causes:

  • Required fields are missing
  • Field values have the wrong type (e.g., string where a number is expected)
  • Values are out of range (e.g., negative quantity)
  • Invalid enum values (e.g., unknown status)

Fix: Read the details array to identify which fields need correction. Each entry specifies the field name and what is wrong.

429 Too Many Requests — RATE_LIMITED

You have exceeded the rate limit for your plan.

json{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded. Try again in 8 seconds.",
    "status": 429
  }
}

Response headers:

HeaderExampleDescription
X-RateLimit-Limit100Your per-minute limit
X-RateLimit-Remaining0Requests left in this window
X-RateLimit-Reset1700000068When the window resets (Unix timestamp)
Retry-After8Seconds to wait before retrying

Fix: Wait for the duration specified in the Retry-After header, then retry. If you consistently hit limits, consider upgrading your plan or optimizing your request patterns with pagination and caching.

500 Internal Server Error — INTERNAL_ERROR

An unexpected error occurred on the server.

json{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred. Please try again.",
    "status": 500
  }
}

Fix: Retry the request after a short delay. If the error persists, contact support at support@zivvy.xyz with your request ID from the X-Request-Id response header.

Handling errors in code

Python

pythonimport requests
import time

def api_request(url, headers, max_retries=3):
    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)

        if response.status_code == 200:
            return response.json()

        error = response.json().get("error", {})

        if response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", 10))
            print(f"Rate limited. Retrying in {retry_after}s...")
            time.sleep(retry_after)
            continue

        if response.status_code >= 500:
            wait = 2 ** attempt
            print(f"Server error. Retrying in {wait}s...")
            time.sleep(wait)
            continue

        # Client errors (4xx) — do not retry
        raise Exception(
            f"API error {error.get('code')}: {error.get('message')}"
        )

    raise Exception("Max retries exceeded")

JavaScript

javascriptasync function apiRequest(url, apiKey, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, {
      headers: { "X-API-Key": apiKey },
    });

    if (response.ok) {
      return response.json();
    }

    const { error } = await response.json();

    if (response.status === 429) {
      const retryAfter = parseInt(response.headers.get("Retry-After") || "10");
      console.log(`Rate limited. Retrying in ${retryAfter}s...`);
      await new Promise((r) => setTimeout(r, retryAfter * 1000));
      continue;
    }

    if (response.status >= 500) {
      const wait = Math.pow(2, attempt) * 1000;
      console.log(`Server error. Retrying in ${wait}ms...`);
      await new Promise((r) => setTimeout(r, wait));
      continue;
    }

    // Client errors — do not retry
    throw new Error(`API error ${error.code}: ${error.message}`);
  }

  throw new Error("Max retries exceeded");
}

Retry guidance

Not all errors should be retried. Follow these rules:

StatusRetry?Strategy
401NoFix your API key
403NoFix key scope or permissions
404NoFix the resource ID or URL
422NoFix the request body
429YesWait for Retry-After seconds, then retry
500YesExponential backoff: 1s, 2s, 4s, up to 3 retries

Idempotency

For write operations (POST, PUT, PATCH), you can include an Idempotency-Key header to safely retry requests without creating duplicate resources:

bashcurl -X POST https://api.zivvy.dev/v1/orders \
  -H "X-API-Key: zk_live_your_key_here" \
  -H "Idempotency-Key: order-abc-123-unique" \
  -H "Content-Type: application/json" \
  -d '{"customer": "CUST-001", "items": [{"item_code": "IT-00001", "qty": 2}]}'

If a request with the same Idempotency-Key has already been processed, the API returns the original response instead of processing the request again. Idempotency keys expire after 24 hours.

This is especially useful when retrying after network timeouts, where you cannot be sure whether the server received and processed the original request.

Request ID

Every API response includes an X-Request-Id header containing a unique identifier for that request:

X-Request-Id: req_a1b2c3d4e5f6

Include this ID when contacting support about a specific error. It helps the team locate the exact request in the logs.