Errors
What the SDK throws (CodesparApiError, TimeoutError) and the guards for coded tool results.
Errors
Two classes cover every transport failure, and five guards cover the coded refusals a tool result can carry. A method's own page says which of these it can throw; the rules are the same everywhere:
- A non-2xx answer from a method that talks to the backend directly (
create,send,sendStream,proxyExecute,paymentStatus,verificationStatus, the two streams,authorize) throwsCodesparApiErrorwith the parsed body. - A request that never reaches the backend (DNS, TLS, a refused connection) throws
CodesparApiErrorwithstatus: 0and the originalfetchrejection ascause. - A request that exceeds its timeout throws
TimeoutError. Streams use an idle timeout that resets on every complete SSE frame. executedoes not throw for an HTTP error: it returns{ success: false, error }. The typed wrappers (charge,ship,ledger,issue,shop,discover,connectionWizard) turn that into a plainErrorwhose message starts with the method name.connectionsandcloseswallow transport failures on purpose.
CodesparApiError
new CodesparApiError(message: string, { status, code?, body?, cause? })The structured exception for every backend answer the SDK refuses. Branch on code, never on the message text: the message is for logs and the SemVer promise covers status, code and body, not the wording.
| Field | Type | Description |
|---|---|---|
status | number | The HTTP status; 0 for a network error that never reached the backend |
code | string | undefined | The backend's error code (mocks_not_permitted, policy_denied, ...) when the body carried one; the legacy error field is honored when code is absent |
body | unknown | The parsed JSON body, or the raw text when it was not JSON |
cause | unknown | For status: 0, the original TypeError or DOMException from fetch |
name | "CodesparApiError" |
try {
await .("Charge R$150 via Pix");
} catch () {
if ( instanceof ) {
if (. === 0) .("network:", .);
else .(., ., .);
} else {
throw ;
}
}interface CodesparApiErrorOptions {
status: number;
code?: string;
body?: unknown;
cause?: unknown;
}
class CodesparApiError extends Error {
readonly status: number;
readonly code?: string;
readonly body?: unknown;
}instanceof works across transpilers and realms: the constructor restores the prototype chain explicitly. The reserved code namespace is the hosted test-mode wire contract; code that extends this class should prefix its own codes (myapp.policy_denied).
Related Errors for the codes the API answers with, Debugging for what to quote (X-Request-Id first).
TimeoutError
new TimeoutError(timeoutMs: number)Thrown when a unary request exceeds its total timeout, or a stream goes idle past it. timeoutMs is the budget that was exceeded. The class is on the core's main branch (the request-timeout-and-cancellation change) and not in the package on npm, where a timeout surfaces as the underlying abort.
import { TimeoutError } from "@codespar/sdk";
try {
await session.paymentStatusStream("tc_0000000000000000", { timeout: 120_000 });
} catch (e) {
if (e instanceof TimeoutError) console.error(`idle for ${e.timeoutMs} ms`);
}class TimeoutError extends Error {
readonly name: "TimeoutError";
readonly timeoutMs: number;
}Tool-result codes
A tool call the runtime refuses for a governed reason does not fail the HTTP request: it succeeds with a coded output. The five variants are inert wire shapes; the guards below turn unknown into one of them so a caller branches without casting. Each guard checks the code discriminant and the variant's required sibling fields, so a well-formed code with a missing rule_id returns false rather than narrowing on the discriminant alone.
| Code | Output fields | Meaning |
|---|---|---|
policy_denied | rule_id, message | A policy rule refused the call |
approval_required | approval_id, expires_at, message | Held for a human approval |
mocks_exhausted | message | A stateful mock list ran out |
mocks_engine_error | message | The mocks engine failed |
tool_not_mocked | tool_name, message | Strict test mode, and this tool had no mock |
TOOL_RESULT_CODES
TOOL_RESULT_CODES: ReadonlySet<ToolResultCode>The set of the five codes, for a membership test on a string.
ToolResultCode
ToolResultCode: { PolicyDenied, ApprovalRequired, MocksExhausted, MocksEngineError, ToolNotMocked }Both a value (the five constants) and a type (their union). ToolResultCode.PolicyDenied is "policy_denied".
isPolicyDenied
isPolicyDenied(value: unknown): value is PolicyDeniedOutputisApprovalRequired
isApprovalRequired(value: unknown): value is ApprovalRequiredOutputisMocksExhausted
isMocksExhausted(value: unknown): value is MocksExhaustedOutputisMocksEngineError
isMocksEngineError(value: unknown): value is MocksEngineErrorOutputisToolNotMocked
isToolNotMocked(value: unknown): value is ToolNotMockedOutputassertExhaustiveToolResult
assertExhaustiveToolResult(value: never): neverThe exhaustiveness witness: pass the narrowed value in the default branch of a switch over ToolResultCode, and the file stops compiling the day a sixth variant lands without a handler. At runtime it throws, so a hostile cast does not silently pass an unknown code through.
for await (const of .("Pay R$1.50 to the example key")) {
if (. !== "tool_result") continue;
const = ..;
if (()) .("denied by rule", .);
else if (()) .("approval", ., "until", .);
else if (() || ()) .("mocks:", .);
else if (()) .("no mock for", .);
}interface PolicyDeniedOutput { code: "policy_denied"; rule_id: string; message: string }
interface ApprovalRequiredOutput { code: "approval_required"; approval_id: string; expires_at: string; message: string }
interface MocksExhaustedOutput { code: "mocks_exhausted"; message: string }
interface MocksEngineErrorOutput { code: "mocks_engine_error"; message: string }
interface ToolNotMockedOutput { code: "tool_not_mocked"; tool_name: string; message: string }
type ToolResultOutcome =
| PolicyDeniedOutput
| ApprovalRequiredOutput
| MocksExhaustedOutput
| MocksEngineErrorOutput
| ToolNotMockedOutput;Related Test mode for mocks and strict mode, Guardrails for policies and approvals.