Skip to content

Error Handling ​

The error format, status codes, and retry strategies for the Creed Space API.

Error Response Format ​

Error bodies take one of four shapes, depending on the error. Branch on the HTTP status code first, then read the body shape for that status.

Authentication and scope errors (RFC 7807) ​

API-key authentication (401 invalid_api_key) and scope (403 scope_required) errors follow the RFC 7807 problem+json format and are returned with Content-Type: application/problem+json:

json
{
  "type": "https://api.creed.space/problems/invalid-api-key",
  "title": "Invalid API Key",
  "status": 401,
  "detail": "API key is invalid or not found.",
  "instance": "/api/v1/safety/evaluate",
  "error_code": "INVALID_API_KEY",
  "reason": "invalid",
  "docs_url": "https://docs.creed.space/authentication"
}

The problem type is a URI of the form https://api.creed.space/problems/<slug>. The slugs in the table below identify the problem type; in the type URI they appear kebab-cased, so invalid_api_key becomes .../problems/invalid-api-key. Match on the full type URI as returned, rather than reconstructing it from the snake_case slug.

Validation errors (422) ​

A request body that fails validation returns 422 with the failing fields:

json
{
  "detail": [
    {"loc": ["body", "input_text"], "msg": "Field required", "type": "missing"}
  ],
  "error": "Validation error"
}

Rate limit errors (429) ​

json
{
  "error": "rate_limit_exceeded",
  "message": "Too many requests. Please try again later.",
  "retry_after": 30
}

The response also carries a Retry-After header (seconds) and the X-RateLimit-* headers described in Rate Limits.

That body comes from the per-key limit. The per-IP limit that runs before authentication returns a nested body instead:

json
{
  "error": {
    "type": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Too many requests. Please try again later.",
    "retry_after": 30
  },
  "detail": "Rate limit exceeded"
}

Both carry Retry-After, so branch on the 429 status and read that header rather than parsing the body.

Other HTTP errors ​

Other errors raised by an endpoint, such as 400, 404 or 503, use a flat shape:

json
{
  "error": "Evaluation timed out",
  "status_code": 503
}

An unexpected server failure returns 500 with {"error": "Internal server error", "message": "..."}.

Error Codes ​

StatusCodeBody shapeDescription
401invalid_api_keyproblem+jsonKey is invalid, missing, badly formatted or revoked (see reason)
403scope_requiredproblem+jsonKey lacks the required permission scope
404—flatRequested resource does not exist
422—validationRequest body validation failed
429rate_limit_exceededrate limitRequest rate limit exceeded
500—flatUnexpected server error
503—flatService or evaluation temporarily unavailable; fail closed

Handling Errors ​

Python (REST) ​

python
import logging
import time

import httpx

logger = logging.getLogger(__name__)
http = httpx.Client(
    base_url="https://api.creed.space",
    headers={"X-API-Key": "cs_live_..."},
    timeout=30.0,
)


def evaluate(text: str) -> dict:
    response = http.post("/api/v1/safety/evaluate", json={"input_text": text})

    if response.status_code == 401:
        # Invalid or missing API key (problem+json)
        raise RuntimeError(f"Auth failed: {response.json().get('detail')}")

    if response.status_code == 403:
        raise RuntimeError(f"Missing scope: {response.json().get('detail')}")

    if response.status_code == 422:
        # Invalid request body
        raise ValueError(f"Validation failed: {response.json().get('detail')}")

    if response.status_code == 429:
        retry_after = int(response.headers.get("Retry-After", "1"))
        logger.warning("Rate limited, retry after %ss", retry_after)
        time.sleep(retry_after)
        return evaluate(text)

    response.raise_for_status()
    return response.json()["evaluation"]

Python SDK ​

The Python SDK raises typed errors from creed_sdk.errors:

python
from creed_sdk.errors import AuthenticationError, CreedError, RateLimitError, TimeoutError

try:
    result = await client.decide(tool_name="send_email", arguments={"to": "user@example.com"})
except AuthenticationError:
    logger.error("API key error")
    raise
except RateLimitError as e:
    logger.warning("Rate limited, retry after %ss", e.retry_after)
    raise
except TimeoutError:
    logger.error("Request timed out")
    raise
except CreedError as e:
    logger.error("API error: %s (status %s)", e.code, e.status_code)
    raise

JavaScript (REST) ​

typescript
async function evaluate(text: string): Promise<unknown> {
    const response = await fetch('https://api.creed.space/api/v1/safety/evaluate', {
        method: 'POST',
        headers: { 'X-API-Key': 'cs_live_...', 'Content-Type': 'application/json' },
        body: JSON.stringify({ input_text: text }),
    });

    if (response.status === 401 || response.status === 403) {
        const problem = await response.json();
        throw new Error(`Auth failed: ${problem.detail}`);
    }

    if (response.status === 422) {
        const body = await response.json();
        throw new Error(`Validation failed: ${JSON.stringify(body.detail)}`);
    }

    if (response.status === 429) {
        const retryAfter = Number(response.headers.get('Retry-After') ?? '1');
        await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
        return evaluate(text);
    }

    if (!response.ok) {
        throw new Error(`Safety check failed with status ${response.status}`);
    }

    return (await response.json()).evaluation;
}

The JavaScript SDK throws CreedError, AuthenticationError, RateLimitError (with retryAfter) and ServerError.

Retry Strategies ​

Neither SDK retries automatically. Retry only on 429 and 5xx, never on 4xx validation or auth errors. A governance decide() call records a new run and action each time, so retry it with care.

Simple Retry ​

Using the evaluate helper from above:

python
def evaluate_with_retry(text: str, max_retries: int = 3) -> dict:
    for attempt in range(max_retries):
        try:
            return evaluate(text)
        except httpx.HTTPStatusError as e:
            if e.response.status_code < 500 or attempt == max_retries - 1:
                raise
            time.sleep(1 * (attempt + 1))

Circuit Breaker ​

For production systems, use a circuit breaker pattern:

python
from circuitbreaker import circuit


@circuit(failure_threshold=5, recovery_timeout=30)
def evaluate_safe(text: str) -> dict:
    return evaluate(text)

Debugging ​

Enable Logging ​

python
import logging

logging.basicConfig(level=logging.DEBUG)
logging.getLogger("httpx").setLevel(logging.DEBUG)

Check Response Headers ​

Every response carries an X-Request-ID header. Log it with the status code:

python
response = http.post("/api/v1/safety/evaluate", json={"input_text": text})
if response.is_error:
    print(f"Request ID: {response.headers.get('X-Request-ID')}")
    print(f"Status: {response.status_code}")

Getting Help ​

When errors persist:

  1. Check that the API is up: GET https://api.creed.space/health returns 200 when the service is healthy
  2. Include the request ID when contacting support
  3. Review your Dashboard for usage issues

Creed Space API documentation. Privacy, Terms and Compliance live on creed.space.