Skip to main content
The SDK exposes money tasks through run() and structured records through forUser(id).data. Read this page when porting to another runtime or inspecting the requests the SDK sends. Schemas are in the API reference tab. Four HTTP calls and one browser redirect: the token exchange, interact (blocking or streaming, one call on two routes), the approvals poll, and user-action. Consent is a navigation, never an API call. Two optional GETs sit beside them: /connect/tool-definition (unauthenticated, static) and /connect/apps/me (the app self-read). Two base URLs appear below. Server requests go to Railway; consent and approval pages live on the console. Set PAYMAN_BASE_URL to the Railway origin when using the SDK, especially older builds that default to the unavailable custom hostname. The SDK discovers the console origin from your app registration. Confidential clients send the first two together. Public OAuth clients send their token in x-paygent-connect-grant without an app key; omitting a key does not make a confidential grant public. Hosted /mcp uses Authorization: Bearer instead of these REST headers. When your app offers Plaid and multi-provider wallets are enabled, customers can add several banks inside one Plaid session. They approve each bank with its own sign-in, then finish the session. Payman saves each bank separately and includes all of those bank connections in the app’s approved wallet. Plaid’s optional phone sign-in is separate from signing in to Payman or approving access at a bank. Build the link on your server and navigate top-level. No iframe, no popup: the customer is signing in to Payman. The SDK’s popup consent depends on the SDK-served callback page; hand-rolled flows keep the plain navigation.
Node
scopes defaults to interact. Request financial_data:read explicitly for structured financial records, or use the comma-separated interact,financial_data:read value when both permissions are needed. OAuth authorization uses the singular scope parameter with space-separated values. Existing grants need new consent before they can use additional permissions.

Structured read endpoints

On an enabled environment, GET /connect/data/connections discovers the connections shared by the grant. POST /connect/reads queues a typed read and returns a durable runId; GET /connect/reads/{runId} returns status and saved pages. All three require financial_data:read. The SDK calls these through forUser(id).data. See the data guide for complete SDK and MCP examples, native Plaid sync, partial results, and checkpoint storage.

Complete the callback

On your callback, verify state first:
Node
state is your CSRF token and nobody checks it for you. Verify it before code, before error, before anything leaves your server. An unknown or reused state is somebody else’s redirect: exchange nothing.

POST /connect/token

The only credential call in the handshake. The browser is not part of it.
Node
If the customer already had a live grant for this app, consenting again replaces it. You never hold two credentials for one connection.

When the customer connects more than one account

One consent can seat several accounts, and a later one can add to a connection you already hold. Three fields on this response say what happened. Nothing else in your integration changes. See the Wallet.
grantToken appears on this response only. Store it encrypted, server-side. Never retry the exchange: the code is single use and burns even on a failed attempt. Start the flow again instead.

POST /connect/interact

Node
Render message labelled as the customer’s own agent. POST /connect/interact/stream is the same call on a second route: that same customer-side agent’s NDJSON relayed verbatim, one JSON object per line, with x-paygent-environment as the stream’s only environment signal.

The routing statuses

A customer with several connected accounts has a Wallet, and two more status values come out of it. Both are ordinary 200s. provider takes an institution type (BREX) or an account label, exactly as a candidate names it. A value matching nothing, or several things, gets the same ask back rather than a guess.

Not running it twice

Both interact routes accept an optional Idempotency-Key header: 16 to 255 URL-safe characters, minted by you, one per customer intent. The same key presented again by the same grant is answered with the first turn’s stored response, byte for byte, and carries idempotency-replayed: true. The same key with a different body is refused rather than resolved.

Result names, wire and SDK

The served tool definition names four results; the SDK renames two of them. GET /connect/tool-definition carries this mapping machine-readable as wireMapping. The two routing statuses above also land in ok, with the wire’s status kept on result.agentStatus.

GET /connect/approvals/{ref}

Poll the ticket the interact response named.
Node
approvalUrl is present only while state is pending. Send the customer there: the owner approves on Payman’s page with an emailed code, and there is no endpoint through which your app could approve or submit one. A 404 is a uniform refusal, never a verdict; keep polling until the clock runs out. The wire reports five states. consumed means the gate spent the ticket, which happens before the bank is called, so the SDK derives two states from it:

POST /connect/interact/user-action/{userActionId}/{action}

A run can stop to ask a question: which vendor, confirm this transfer. The question arrives as a frame on the stream carrying userActionId, a field key, and the options. Answer it here; the path accepts submit, cancel, resend and expired, and apps send the first three.
Node
Only submit carries a body, and the body is always a chosen option id: never free text, never a code. cancel and resend post empty. That is the whole protocol. After the consent return, answer the question the customer asked before the detour: the SDK stashes and replays it for you; hand-rolled, the stash and the replay are yours.