/docs/agents/advanced
Advanced usage
The Obscura constructor accepts six options. Together they cover local
development, custom network stacks, retry/timeout tuning, and standard
RequestInit pass-through.
Point at a different Obscura instance
For local development, point at your own Obscura backend:
const agent = new Obscura({
apiKey: process.env.OBSCURA_KEY!,
baseUrl: "http://localhost:3000",
});Useful for end-to-end testing the full agent → Obscura → merchant loop against a local Next.js dev server without touching production infra.
Use a custom fetch
On older Node, edge runtimes, or in tests, inject your own fetch:
import { fetch as undiciFetch } from "undici";
const agent = new Obscura({
apiKey: process.env.OBSCURA_KEY!,
baseUrl: process.env.OBSCURA_BASE_URL!,
fetch: undiciFetch as typeof fetch,
});The SDK uses the injected fetch for every HTTP call it makes — both the outbound request to the merchant and the internal sign request to Obscura — so behavior stays consistent across runtimes.
Tune timeouts and retries
The Umbra mixer transfer involves a Groth16 ZK proof + Arcium MPC callback round-trip — typically 10–25 seconds, P99 around 45 seconds on a cold prover cache. The SDK ships sensible defaults but exposes every knob.
const agent = new Obscura({
apiKey: process.env.OBSCURA_KEY!,
baseUrl: process.env.OBSCURA_BASE_URL!,
// Per-request timeout for /api/x402/sign. Default 60_000.
signTimeoutMs: 90_000,
// Auto-retry attempts on transient failures (network, 5xx, timeout,
// rate_limited). Default 2. Terminal errors never retry — see Errors.
signMaxRetries: 3,
// First retry backoff in ms; doubles each subsequent retry. Default 500.
signRetryBaseMs: 750,
});Pass through init options
agent.fetch() accepts the standard RequestInit — headers, method,
body, signal (AbortController), etc. Your headers survive the payment
retry.
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
const res = await agent.fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: "solana news" }),
signal: controller.signal,
});Three subtleties worth knowing:
- Headers survive the retry. Internally the SDK merges its
PAYMENT-SIGNATUREheader into theHeadersobject you passed; it doesn't replace yours. SoContent-Type,Accept, and any customX-headers reach the merchant on the paid request. - Body is re-sent as-is. The SDK forwards the same
initto the paid retry. If you passbody: ReadableStream, that stream needs to be re-readable — passbody: Buffer/ string /URLSearchParamsinstead for safety. - AbortSignal cancels both hops. The signal aborts whichever HTTP call is in flight — the initial 402 probe OR the paid retry.
Full constructor reference
See API reference for ObscuraOptions,
ObscuraError, and every exported type.