The account lane
What happens between sign-in and "ready" — the automatic provisioning ladder, how requirement decides how far it runs, and why apps only read the lifecycle.
Signing in answers "who is this?". It does not answer "can this identity hold and move money yet?". Between the two sits real setup work: account infrastructure has to be connected, an identity record confirmed, an Account reserved, and — when the app needs to transact — the Account activated.
Capxul's answer to that gap is the account lane: the SDK runs the whole ladder itself, automatically, after sign-in. Apps never chain setup calls; they render a single readiness value.
The ladder
When the lane runs, it climbs a short ladder of steps, each one idempotent — already-satisfied rungs are skipped:
- Account infrastructure — connect the account infrastructure for the signed-in user.
- Identity — ensure the Capxul identity record exists for the session.
- Provision — reserve the Account itself. The Account's identity is derived deterministically from the verified email alone, so at this point it already exists as a stable identity and deposit target — before it can transact.
- Activate — make the Account transaction-capable. This rung only runs when the app asked for it (see below) and the client holds a signer that can complete activation.
…and then ready. A failure at any rung parks the lane in a failed state
that names the step and carries a CapxulError; nothing is left half-driven,
and retrying resumes from where it stopped rather than starting over.
requirement decides how far the lane runs
The requirement option you pass at client creation is the account target —
it tells the lane where "ready" is for your app:
| Requirement | The lane runs | Use when |
|---|---|---|
"none" | Not at all. | The app only needs auth, session, and profile. |
"counterfactual" | Through provision. | The app needs a stable account identity or deposit target, but not transactions yet. |
"deployed" | Through activation. | The app must show or use a transaction-capable money surface. |
This is why requirement is chosen once, at bootstrap, rather than per call:
it is not a request parameter, it is a statement about what your product's
screens assume. A dashboard that renders balances can live on
"counterfactual"; a screen with a Pay button needs "deployed".
One nuance for "deployed": activation needs a signer. A client with a
"deployed" requirement but no signer runs the lane to the provisioned
(counterfactual) state and parks there — a dedicated approval surface owns the
activation instead. The lifecycle's canTransact flag tells you which side of
that line you are on.
The lane starts itself
There is no startProvisioning() call in app code. After a successful
verifyOtp, whenever a session exists, the requirement is not "none", and
the target is not yet met, the lane kicks off automatically. Reading the
lifecycle — via client.account.getLifecycle() or the React hook — also
idempotently starts or resumes it. The lane runs at most once at a time; reads
never trigger duplicate climbs.
Apps read one value: AccountLifecycle
The public contract is a single union:
type AccountLifecycle =
| { status: "loading" }
| { status: "settingUp"; step: AccountSetupStep }
| { status: "ready"; accountId: string; canTransact: boolean }
| { status: "failed"; at: AccountSetupStep; error: CapxulError };The settingUp steps are deliberately plain UI words — "connecting",
"confirmingIdentity", "registering", "activating" — mapped from the
internal ladder. They are labels to render, not instructions to call anything.
("confirmingIdentity" stays in the vocabulary for contract stability even
though the current ladder no longer produces it.)
In React, useCapxulAccountLifecycle() is the whole integration: it polls
every couple of seconds while the lane is loading or setting up, stops when it
settles, and exposes a retry for the failed state:
const { lifecycle, retry } = useCapxulAccountLifecycle();
switch (lifecycle.status) {
case "loading":
return <Checking />;
case "settingUp":
return <SetupProgress step={lifecycle.step} />;
case "failed":
return <SetupFailed error={lifecycle.error} onRetry={() => retry()} />;
case "ready":
return <MoneyHome canTransact={lifecycle.canTransact} />;
}Why apps never chain provisioning calls
It would be possible to expose the rungs as public methods and let apps drive them. The SDK refuses, for reasons that have already paid for themselves:
- The ladder changes; the contract doesn't. The lane has already been
restructured — steps have been merged away as the platform simplified —
without breaking a single app, because apps only ever rendered
AccountLifecycle. - Choreography is a correctness problem. Ordering, idempotency, resume-after-failure, and not-running-twice are easy to get subtly wrong in app code and are handled once, centrally, in the lane.
- It keeps the surface money-shaped. The rungs touch infrastructure that the consumer vocabulary deliberately does not name. A public step-by-step API would drag that vocabulary into product code.
The trade-off is control: you cannot reorder rungs, insert your own steps, or
intercept the ladder mid-climb. If a product genuinely needs to react to
setup progress, the granularity you get is the step on settingUp — which
has so far been exactly enough to build setup screens.
The bigger picture
Think of the platform as having two gates between a visitor and money:
authentication ("who is this?") and the account lane ("is their money surface
ready?"). They are separate states read from separate hooks, and the second
one is a process, not a flag — which is why it has steps, failure, and retry.
Once lifecycle.status is "ready", everything in
money & accounts is on the table.
Identity & organizations
Who can hold and move money — Capxul users, organizations and their treasuries, members and roles, handles, acting entities, and typed recipient references.
Agent-native
What agent-native means on this platform — agents doing what users and developers can do, through safe structured primitives, and docs built for machine readers.