Error Handling

Handle API errors with typed exceptions and type narrowing
View as Markdown

The Clossir SDK throws typed exceptions for all API errors. Every 4xx and 5xx response from the Clossir API returns an RFC 9457 application/problem+json body, which the SDK parses into a SignumProblemDetailsError.

Catching errors

catch-errors.ts
1import { Signum } from "@signum-tech/sdk";
2import {
3 SignumProblemDetailsError,
4 SignumError,
5} from "@signum-tech/sdk/models/errors";
6
7const signum = new Signum({
8 serverURL: process.env.SIGNUM_API_URL,
9 bearerAuth: process.env.SIGNUM_API_KEY,
10});
11
12try {
13 await signum.attestations.postAttestations({
14 partyId: "pty_abc123",
15 type: "accredited_investor",
16 chainEid: 30101,
17 });
18} catch (e) {
19 if (e instanceof SignumProblemDetailsError) {
20 console.error(e.title); // "Forbidden"
21 console.error(e.status); // 403
22 console.error(e.detail); // "Organization KYC not approved"
23 console.error(e.type); // "urn:signum:error:kyc-required"
24 console.error(e.instance); // "/attestations/abc-123"
25 }
26}

Error hierarchy

Error
└── SignumError (statusCode, body, headers, rawResponse)
├── SignumProblemDetailsError (type, title, status, detail, instance)
└── SignumDefaultError (fallback for non-problem+json responses)
HTTPClientError
├── RequestTimeoutError
├── ConnectionError
├── RequestAbortedError
├── InvalidRequestError
└── UnexpectedClientError

SignumProblemDetailsError is the primary error type — the API standardizes on RFC 9457 for all error responses. SignumDefaultError exists as a fallback but should not occur under normal conditions.

HTTPClientError subtypes represent transport-layer failures (network issues, timeouts) rather than API responses.

Type narrowing

TypeScript narrows the type after an instanceof check, giving you access to the structured fields:

type-narrowing.ts
1try {
2 await signum.transfers.postTransfers({ /* ... */ });
3} catch (e: unknown) {
4 if (e instanceof SignumProblemDetailsError) {
5 // RFC 9457 structured error — all fields available
6 switch (e.status) {
7 case 409:
8 console.log("Conflict — resource already exists");
9 break;
10 case 429:
11 console.log("Rate limited — back off and retry");
12 break;
13 default:
14 console.error(`${e.title}: ${e.detail}`);
15 }
16 } else if (e instanceof SignumError) {
17 // Other HTTP error — has statusCode, body, rawResponse
18 console.error(`HTTP ${e.statusCode}: ${e.body}`);
19 } else {
20 // Network error, timeout, etc.
21 throw e;
22 }
23}

Extension fields

Problem+json responses may include fields beyond the standard RFC 9457 set — for example, the guard middleware includes resolution hints when a prerequisite check fails. Access these via the data$ property:

extension-fields.ts
1if (e instanceof SignumProblemDetailsError) {
2 const hints = e.data$["resolution_hints"] as string[] | undefined;
3 const checks = e.data$["failed_checks"] as string[] | undefined;
4
5 if (hints) {
6 console.log("To resolve, try:", hints.join(", "));
7 }
8}

Branching on error type

The type field uses URN-style identifiers that categorize the error. Use it for programmatic error handling:

branch-on-type.ts
1if (e instanceof SignumProblemDetailsError) {
2 switch (e.type) {
3 case "urn:signum:error:kyc-required":
4 // redirect user to KYC flow
5 break;
6 case "urn:signum:error:compliance-blocked":
7 // show compliance rejection reason
8 break;
9 case "urn:signum:error:insufficient-balance":
10 // prompt for funding
11 break;
12 default:
13 // unknown error type — show detail to user
14 console.error(e.detail);
15 }
16}

Standalone functions (Result pattern)

The SDK also exposes standalone functions that return Result<T, E> instead of throwing. This is useful for functional/railway-style error handling:

result-pattern.ts
1import { SignumCore } from "@signum-tech/sdk/core.js";
2import { healthGetHealth } from "@signum-tech/sdk/funcs/health-get-health.js";
3
4const client = new SignumCore({
5 serverURL: process.env.SIGNUM_API_URL,
6 bearerAuth: process.env.SIGNUM_API_KEY,
7});
8
9const result = await healthGetHealth(client);
10
11if (!result.ok) {
12 // result.error is the error — no try/catch needed
13 console.error("Health check failed:", result.error);
14 return;
15}
16
17// result.value is the typed response
18console.log("API is healthy:", result.value);

Next steps