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"
}| Field | Required | Description |
|---|---|---|
type | yes | URI identifying the error class. Dereferences to human-readable docs. |
title | yes | Short, human-readable summary of the error class. |
status | yes | HTTP status code. |
detail | no | Human-readable explanation specific to this occurrence. |
instance | no | URI of the specific request that failed. |
trace_id | no | CoreSDK 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 suffix | Cause |
|---|---|
auth/missing-token | No Authorization header present |
auth/invalid-token | JWT signature invalid or malformed |
auth/token-expired | JWT exp claim is in the past |
auth/unknown-issuer | iss claim not in the configured allow-list |
auth/jwks-unavailable | JWKS endpoint unreachable and cache expired |
Policy errors (403)
Returned when the Rego policy evaluates to deny.
type suffix | Cause |
|---|---|
policy/denied | Policy returned deny for this user/action/resource |
policy/missing-rule | No 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 suffix | Cause |
|---|---|
tenant/not-found | tenant_id claim does not match a known tenant |
tenant/suspended | Tenant account is suspended |
tenant/plan-limit | Request 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 dictRust
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.reasonfield available to your handler on an allow decision - Architecture — how audit logs capture denied decisions asynchronously
Request Context
Where CoreSDK places authenticated claims after middleware runs. FastAPI uses request.state.coresdk_user, Flask uses g.claims, Django uses request.coresdk_claims.
PII & Secrets Masking
Automatic redaction of API keys, tokens, passwords, emails, SSNs, and credit card numbers before spans leave the process. Zero-tolerance policy enforced by SpanProcessor, fuzz-tested with cargo fuzz.