Skip to main content
CoreSDK
Reference

Error Reference

RFC 9457 error responses from CoreSDK.

Error Reference

CoreSDK returns application/problem+json errors following RFC 9457. Every error includes a trace_id linking it to the corresponding OTEL span.

Error shape

{
  "type": "https://coresdk.dev/errors/<error-code>",
  "title": "Human-readable title",
  "status": 401,
  "detail": "Specific message for this occurrence",
  "instance": "/api/orders/ord_7mP3",
  "trace_id": "01HX7KQMB4NWE9P6T2JS0RY3ZV",
  "tenant": "acme",
  "timestamp": "2026-03-19T14:22:01Z"
}

Error codes

CodeStatusCause
jwt-missing401No Authorization header
jwt-invalid401Signature verification failed
jwt-expired401Token past exp claim
jwt-unknown-key401kid not in JWKS
policy-denied403Rego policy returned allow = false
tenant-not-found403Tenant ID not provisioned
rate-limit-exceeded429Per-tenant rate limit hit
internal-error500CoreSDK internal error

Policy denied error

403 errors include the policy action and evaluation context:

{
  "type": "https://coresdk.dev/errors/policy-denied",
  "title": "Authorization Denied",
  "status": 403,
  "detail": "Action 'orders:write' denied for role 'guest'",
  "instance": "/api/orders",
  "trace_id": "01HX7KQMB4NWE9P6T2JS0RY3ZV",
  "tenant": "acme",
  "policy": {
    "action": "orders:write",
    "policy_package": "coresdk.authz",
    "user_role": "guest"
  }
}

Python — raising RFC 9457 errors

from coresdk.errors._rfc9457 import ProblemDetailError

raise ProblemDetailError(
    type_uri="https://coresdk.dev/errors/policy-denied",
    title="Authorization Denied",
    status=403,
    detail="Action 'orders:write' denied for role 'guest'",
)

Rust — returning RFC 9457 responses

use coresdk_engine::ProblemDetail;

// In an Axum handler:
return ProblemDetail::unauthorized("Missing bearer token").into_response();

Custom error mapper

Override the default error shape:

use coresdk_engine::error::{ErrorMapper, ProblemDetail, AuthError};

struct MyMapper;

impl ErrorMapper for MyMapper {
    fn map(&self, err: AuthError) -> ProblemDetail {
        match err {
            AuthError::PolicyDenied(d) => ProblemDetail::new("policy-denied")
                .status(403)
                .title("Access Denied")
                .detail(format!("You don't have permission to {}", d.action)),
            _ => ProblemDetail::default_for(err),
        }
    }
}
from coresdk.error import ErrorMapper, ProblemDetail, AuthError

class MyMapper(ErrorMapper):
    def map(self, err: AuthError) -> ProblemDetail:
        if isinstance(err, AuthError.PolicyDenied):
            return (
                ProblemDetail.new("policy-denied")
                .status(403)
                .title("Access Denied")
                .detail(f"You don't have permission to {err.action}")
            )
        return ProblemDetail.default_for(err)
import "github.com/coresdk/sdk/error"

type MyMapper struct{}

func (m *MyMapper) Map(err sdkerror.AuthError) sdkerror.ProblemDetail {
    if denied, ok := err.(sdkerror.PolicyDenied); ok {
        return sdkerror.NewProblemDetail("policy-denied").
            Status(403).
            Title("Access Denied").
            Detail(fmt.Sprintf("You don't have permission to %s", denied.Action))
    }
    return sdkerror.DefaultFor(err)
}
import { ErrorMapper, ProblemDetail, AuthError } from "@coresdk/sdk/error";

const myMapper: ErrorMapper = {
  map(err: AuthError): ProblemDetail {
    if (err.code === "policy-denied") {
      return ProblemDetail.new("policy-denied")
        .status(403)
        .title("Access Denied")
        .detail(`You don't have permission to ${err.action}`);
    }
    return ProblemDetail.defaultFor(err);
  },
};

Client-side handling

let res = client.get("/api/orders").send().await?;
if !res.status().is_success() {
    let problem: serde_json::Value = res.json().await?;
    // problem["type"], problem["title"], problem["detail"], problem["trace_id"]
    eprintln!("[{}] {}: {}", problem["trace_id"], problem["title"], problem["detail"]);
}
import httpx

res = httpx.get("/api/orders")
if not res.is_success:
    problem = res.json()
    # problem["type"], problem["title"], problem["detail"], problem["trace_id"]
    print(f"[{problem['trace_id']}] {problem['title']}: {problem['detail']}")
res, err := http.Get("/api/orders")
if err != nil {
    log.Fatal(err)
}
if res.StatusCode >= 400 {
    var problem map[string]interface{}
    json.NewDecoder(res.Body).Decode(&problem)
    // problem["type"], problem["title"], problem["detail"], problem["trace_id"]
    log.Printf("[%s] %s: %s", problem["trace_id"], problem["title"], problem["detail"])
}
const res = await fetch("/api/orders");
if (!res.ok) {
  const problem = await res.json();
  // problem.type, problem.title, problem.detail, problem.trace_id
  console.error(`[${problem.trace_id}] ${problem.title}: ${problem.detail}`);
}

The trace_id lets you look up the full request trace in your observability backend.

On this page