Capxul Docs
Core client

Node & servers

Use the core client outside React — in scripts, servers, and agent backends — and hand a pre-built client to CapxulProvider.

The React hooks are one consumer of the core client. Anywhere without a React tree — a Node script, a server route, a CLI, an agent backend — you create the client yourself with createCapxulClient and call its method bundles directly.

Create the client in a script

import { createCapxulClient } from "@capxul/sdk";

const result = await createCapxulClient({
  publishableKey: process.env.CAPXUL_PUBLISHABLE_KEY!,
  requirement: "counterfactual",
});

if (!result.ok) {
  throw result.error;
}
const client = result.value;

// Email OTP works the same as in the browser: request a code, then verify it.
await client.auth.signIn({ email: "ops@example.com" });
const otpCode = process.argv[2]!; // the 6-digit code from the sign-in email
const session = await client.auth.verifyOtp({ email: "ops@example.com", code: otpCode });
if (!session.ok) {
  throw session.error;
}

const account = await client.accounts.read();
if (account.ok) {
  console.log(`${account.value.balance.value} ${account.value.balance.currency}`);
}

Two things differ from the browser:

  • No exceptions for domain failures. Every method returns Promise<CapxulResult<T>> — narrow on ok instead of wrapping calls in try/catch. See the result contract.
  • Sessions live in memory. In Node, the default session cache lasts for the lifetime of the process. Sign in at startup; a new process starts signed out.

Requirement semantics

requirement declares, at creation time, how ready the signed-in user's Account must become. The client works toward that target automatically after sign-in — no per-call readiness arguments anywhere else.

"none" (default)

No account setup runs. Use it for tooling that only needs auth or profile reads and never touches money.

"counterfactual"

After sign-in, the account lane prepares the user's Account so it can receive money immediately; full activation is deferred. This is the usual choice for scripts and servers that read balances or move money.

"deployed"

The Account is fully activated during setup. Outside the browser this requires a signercreateCapxulClient rejects requirement: "deployed" without one. Build a Node signer from a private key:

import { createCapxulClient } from "@capxul/sdk";
import { localPrivateKeySigner } from "@capxul/sdk/node";

const result = await createCapxulClient({
  publishableKey: process.env.CAPXUL_PUBLISHABLE_KEY!,
  requirement: "deployed",
  signer: localPrivateKeySigner({ privateKey: process.env.SIGNER_PRIVATE_KEY! as `0x${string}` }),
});

Browser apps omit signer for "deployed" — the SDK provisions one automatically. Track setup progress with client.account.getLifecycle() (the same lifecycle useCapxulAccountLifecycle polls in React).

Server-bootstrapped React: inject the client

CapxulProvider accepts exactly one of publishableKey or client. When your server (or test harness) bootstraps the client before React renders, pass the pre-built client instead of a key:

import { CapxulProvider } from "@capxul/sdk-react";
import type { CapxulClient } from "@capxul/sdk";

export function App({ client }: { client: CapxulClient }) {
  return (
    <CapxulProvider client={client}>
      <Dashboard />
    </CapxulProvider>
  );
}

Injected-client mode changes the provider contract:

  • Ready immediately. There is no bootstrapping phase; useCapxul() reports ready from the first render, and data hooks run right away.
  • Lifecycle stays with you. The provider never closes an injected client — you created it, you own it.
  • No requirement or signer props. Those are inputs to createCapxulClient; you already made those choices when you built the client. Passing them alongside client is a type error.
  • Swapping the client prop resets the cache. The provider clears Capxul-scoped queries when the client identity changes, so data from one client never bleeds into another.

Passing both publishableKey and client — or neither — throws at render.

Building for agents

An agent backend is just a Node consumer: create the client, call the bundles. For agent-driven payments specifically, use the client.workbench flow — draftsimulaterequestApprovalexecute — which keeps a human in the approval loop, or skip the client entirely and connect to the Capxul MCP server, which exposes these operations as tools.

On this page