Skip to content

RFC 9457 Error Handling & Typed SDK Exceptions

10. RFC 9457 Problem Details & Typed SDK Error Handling

Section titled “10. RFC 9457 Problem Details & Typed SDK Error Handling”

All error responses from DID.is return application/problem+json conforming to RFC 9457.

{
"type": "https://www.w3.org/ns/did#NOT_FOUND",
"title": "Not found",
"status": 404,
"detail": "https://example.com/.well-known/did.json returned HTTP 404",
"code": "NOT_FOUND",
"trace": [
{
"stage": "web.fetch",
"label": "Fetch DID document",
"status": "FAIL",
"startedUs": 140,
"durationUs": 48210,
"detail": "HTTP 404 Not Found"
}
]
}

Problem code Problem type Base HTTP Status Root Cause
INVALID_DID https://www.w3.org/ns/did# 400 Syntax violates W3C ABNF parsing.
INVALID_DID_URL https://www.w3.org/ns/did# 400 Malformed fragment, query, or path characters.
INVALID_OPTIONS https://www.w3.org/ns/did# 400 Unknown query parameters or options.
NOT_FOUND https://www.w3.org/ns/did# 404 Target DID document or URL resource does not exist.
REPRESENTATION_NOT_SUPPORTED https://www.w3.org/ns/did# 406 Client requested an unsupported Accept header.
METHOD_NOT_SUPPORTED https://www.w3.org/ns/did# 501 Method not implemented (supported: key, jwk, web, webvh).
FEATURE_NOT_SUPPORTED https://www.w3.org/ns/did# 501 Path dereferencing (did:.../path) is unsupported.
INVALID_DID_DOCUMENT https://www.w3.org/ns/did# 422 Document id does not match requested DID.
INTERNAL_ERROR https://www.w3.org/ns/did# 500 Unexpected core panic or database failure.
EGRESS_BLOCKED https://did.is/problems# 403 Target IP blocked by anti-SSRF security policy.
UPSTREAM_UNAVAILABLE https://did.is/problems# 502 Upstream host reset connection or timed out (>4s).
INVALID_REQUEST https://did.is/problems# 400 Duplicate JSON member names or malformed request body.
PAYLOAD_TOO_LARGE https://did.is/problems# 413 Ingress body $>512\text{ KiB}$ or upstream body exceeded cap.
UNAUTHORIZED https://did.is/problems# 401 Missing or invalid Bearer token.
RATE_LIMITED https://did.is/problems# 429 Client exceeded rate budget. Check Retry-After.

The TypeScript SDK exports the canonical Problem interface and DidisError class for type-safe error handling:

export interface TraceStage {
stage: string;
label: string;
status: "PASS" | "FAIL" | "WARN" | "INFO" | "SKIP" | string;
startedUs: number;
durationUs: number;
detail: string;
}
export interface Problem {
type: string;
title: string;
status?: number;
detail: string;
code?: string;
trace?: TraceStage[];
/** Retry-After delta-seconds or HTTP-date verbatim from the server */
retryAfter?: string;
}
export class DidisError extends Error {
readonly problem: Problem;
readonly status: number;
}
import { DidisClient, DidisError } from "@didis/client";
const didis = new DidisClient();
try {
await didis.resolve("did:web:unknown-domain-404.example");
} catch (err) {
if (err instanceof DidisError) {
const { code, status, detail, retryAfter } = err.problem;
switch (code) {
case "NOT_FOUND":
console.warn(`Identifier not found (HTTP ${status}): ${detail}`);
break;
case "RATE_LIMITED":
console.warn(`Rate limited. Exponential backoff for ${retryAfter ?? 60} seconds.`);
break;
case "EGRESS_BLOCKED":
console.error(`Security violation: Target domain resolved to a forbidden IP address.`);
break;
default:
console.error(`API Error [${code ?? "UNKNOWN"}]: ${detail}`);
break;
}
} else {
console.error("Non-API network or environment error:", err);
}
}

Python SDK Error Typing & Exception Handling

Section titled “Python SDK Error Typing & Exception Handling”

The Python SDK raises DidisError, which encapsulates the HTTP status code and full RFC 9457 problem dictionary:

from typing import TypedDict, Optional, List, Dict, Any
class TraceStage(TypedDict, total=False):
stage: str
label: str
status: str
startedUs: int
durationUs: int
detail: str
class ProblemDetails(TypedDict, total=False):
type: str
title: str
status: int
detail: str
code: str
trace: List[TraceStage]
retryAfter: Optional[str]
class DidisError(Exception):
status: int
problem: Dict[str, Any]
from didis import DidisClient, DidisError
with DidisClient() as client:
try:
res = client.resolve("did:web:unknown-domain-404.example")
except DidisError as err:
problem = err.problem
code = problem.get("code")
status = err.status
detail = problem.get("detail", "")
retry_after = problem.get("retryAfter")
if code == "NOT_FOUND":
print(f"DID Document was not found ({status}): {detail}")
elif code == "RATE_LIMITED":
print(f"Rate limited. Back off for {retry_after or 60}s.")
elif code == "EGRESS_BLOCKED":
print(f"Anti-SSRF violation: Host resolved to a private/loopback IP.")
else:
print(f"DID.is API Error [{code}]: {detail}")
except Exception as exc:
print(f"Transport/System exception: {exc}")