Skip to main content
CoreSDK
Core Concepts

Error Handling

CoreSDK error format (RFC 9457), ProblemDetailError, built-in error types for auth, policy, and tenant failures, mapping SDK errors to HTTP responses, and customizing error output.

Error Handling

CoreSDK returns errors in RFC 9457 (Problem Details for HTTP APIs) format. Every error the SDK generates is a JSON object with a predictable shape that clients can parse reliably.

RFC 9457 error format

{
  "type": "https://errors.coresdk.io/auth/token-expired",
  "title": "Token Expired",
  "status": 401,
  "detail": "The JWT in the Authorization header expired at 2026-03-19T10:00:00Z.",
  "instance": "/api/orders/42",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
FieldRequiredDescription
typeyesURI identifying the error class. Dereferences to human-readable docs.
titleyesShort, human-readable summary of the error class.
statusyesHTTP status code.
detailnoHuman-readable explanation specific to this occurrence.
instancenoURI of the specific request that failed.
trace_idnoCoreSDK extension — OTEL trace ID for log correlation.

The Content-Type of error responses is always application/problem+json.

Built-in error types

Auth errors (401)

Returned when JWT verification fails.

type suffixCause
auth/missing-tokenNo Authorization header present
auth/invalid-tokenJWT signature invalid or malformed
auth/token-expiredJWT exp claim is in the past
auth/unknown-issueriss claim not in the configured allow-list
auth/jwks-unavailableJWKS endpoint unreachable and cache expired

Policy errors (403)

Returned when the Rego policy evaluates to deny.

type suffixCause
policy/deniedPolicy returned deny for this user/action/resource
policy/missing-ruleNo rule matched the action (fail-closed)

The detail field on policy errors contains the matched rule name and the deny reason if the policy provides one via deny_message.

Tenant errors (403 / 400)

type suffixCause
tenant/not-foundtenant_id claim does not match a known tenant
tenant/suspendedTenant account is suspended
tenant/plan-limitRequest exceeds the tenant's plan quota

ProblemDetailError in Python

The Python SDK exposes ProblemDetailError for raising RFC 9457-shaped errors from application code.

from coresdk.errors._rfc9457 import ProblemDetailError

raise ProblemDetailError(
    type="https://errors.example.com/orders/insufficient-funds",
    title="Insufficient Funds",
    status=402,
    detail="Account balance is below the required amount.",
    instance="/api/orders/42",
)

FastAPI exception handler

Register a handler so ProblemDetailError is automatically serialised to application/problem+json:

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from coresdk.errors._rfc9457 import ProblemDetailError

app = FastAPI()

@app.exception_handler(ProblemDetailError)
async def problem_detail_handler(request: Request, exc: ProblemDetailError):
    return JSONResponse(
        status_code=exc.status,
        content=exc.to_dict(),
        media_type="application/problem+json",
    )

Rust: ProblemDetail type

use coresdk_engine::error::ProblemDetail;

let problem = ProblemDetail {
    r#type: "https://errors.example.com/orders/insufficient-funds".into(),
    title: "Insufficient Funds".into(),
    status: 402,
    detail: Some("Account balance is below the required amount.".into()),
    instance: Some("/api/orders/42".into()),
    trace_id: None,
};

Handling SDK errors in your own code

When you call CoreSDK methods (e.g., validate_token or evaluate_policy outside the middleware chain), errors surface as typed exceptions in Python and as Result types in Rust.

Python

from coresdk.errors._rfc9457 import ProblemDetailError

try:
    decision = await _sdk.validate_token(token)
    # decision.allowed, decision.claims, decision.reason
except ProblemDetailError as e:
    print(f"auth failed [{e.status}]: {e.detail}")
    # e.to_dict() returns the full RFC 9457 dict

Rust

use coresdk_engine::{Engine, error::ProblemDetail};

match engine.validate_token(&token).await {
    Ok(decision) => { /* decision.allowed, decision.claims, decision.reason */ }
    Err(err) => {
        let problem: ProblemDetail = err.into();
        eprintln!("auth failed [{}]: {:?}", problem.status, problem.detail);
    }
}

Customizing error messages in policies

Policy errors can carry a human-readable reason by returning deny_message from your Rego policy. CoreSDK places this string in the detail field.

package orders

default allow = false

allow if {
    input.user.role == "admin"
}

deny_message = "Only admins can access orders." if {
    input.user.role != "admin"
}

Resulting error response:

{
  "type": "https://errors.coresdk.io/policy/denied",
  "title": "Forbidden",
  "status": 403,
  "detail": "Only admins can access orders.",
  "instance": "/api/orders",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}

Do not include sensitive data in deny_message — this string reaches the client.

Next steps

  • Middleware Pipeline — where in the chain errors are generated and how short-circuiting works
  • Request Context — the policy.reason field available to your handler on an allow decision
  • Architecture — how audit logs capture denied decisions asynchronously

On this page