Skip to main content

Errors

What the SDK throws (CodesparApiError, TimeoutError) and the guards for coded tool results.

3 min read
View MarkdownEdit on GitHub

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) throws CodesparApiError with the parsed body.
  • A request that never reaches the backend (DNS, TLS, a refused connection) throws CodesparApiError with status: 0 and the original fetch rejection as cause.
  • A request that exceeds its timeout throws TimeoutError. Streams use an idle timeout that resets on every complete SSE frame.
  • execute does 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 plain Error whose message starts with the method name.
  • connections and close swallow transport failures on purpose.

CodesparApiError

Readnew CodesparApiError(message: string, { status, code?, body?, cause? })
since 0.10.0PythonApiError

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.

FieldTypeDescription
statusnumberThe HTTP status; 0 for a network error that never reached the backend
codestring | undefinedThe backend's error code (mocks_not_permitted, policy_denied, ...) when the body carried one; the legacy error field is honored when code is absent
bodyunknownThe parsed JSON body, or the raw text when it was not JSON
causeunknownFor status: 0, the original TypeError or DOMException from fetch
name"CodesparApiError"
Example
try {
  await .("Charge R$150 via Pix");
} catch () {
  if ( instanceof ) {
    if (. === 0) .("network:", .);
    else .(., ., .);
  } else {
    throw ;
  }
}
CodesparApiErrorOptions
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

Readnew TimeoutError(timeoutMs: number)
main only, not on npmPythonTimeoutError

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.

CodeOutput fieldsMeaning
policy_deniedrule_id, messageA policy rule refused the call
approval_requiredapproval_id, expires_at, messageHeld for a human approval
mocks_exhaustedmessageA stateful mock list ran out
mocks_engine_errormessageThe mocks engine failed
tool_not_mockedtool_name, messageStrict test mode, and this tool had no mock

TOOL_RESULT_CODES

ReadTOOL_RESULT_CODES: ReadonlySet<ToolResultCode>
since 0.10.0PythonTOOL_RESULT_CODES

The set of the five codes, for a membership test on a string.

ToolResultCode

ReadToolResultCode: { PolicyDenied, ApprovalRequired, MocksExhausted, MocksEngineError, ToolNotMocked }
since 0.10.0PythonToolResultCode

Both a value (the five constants) and a type (their union). ToolResultCode.PolicyDenied is "policy_denied".

isPolicyDenied

ReadisPolicyDenied(value: unknown): value is PolicyDeniedOutput
since 0.10.0Pythonis_policy_denied(value)

isApprovalRequired

ReadisApprovalRequired(value: unknown): value is ApprovalRequiredOutput
since 0.10.0Pythonis_approval_required(value)

isMocksExhausted

ReadisMocksExhausted(value: unknown): value is MocksExhaustedOutput
since 0.10.0Pythonis_mocks_exhausted(value)

isMocksEngineError

ReadisMocksEngineError(value: unknown): value is MocksEngineErrorOutput
since 0.10.0Pythonis_mocks_engine_error(value)

isToolNotMocked

ReadisToolNotMocked(value: unknown): value is ToolNotMockedOutput
since 0.10.0Pythonis_tool_not_mocked(value)

assertExhaustiveToolResult

ReadassertExhaustiveToolResult(value: never): never
since 0.10.0Pythonassert_exhaustive_tool_result(value)

The 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.

Example
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", .);
}
The five outputs
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.

Errors | CodeSpar