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.
The consent redirect
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, verifystate first:
Node
POST /connect/token
The only credential call in the handshake. The browser is not part of it.
Node
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.
POST /connect/interact
Node
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 morestatus 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 optionalIdempotency-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
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.
