Node SDK
@avvio/payments v0.8.1: a Node client, the CLI and an MCP server in one package with zero runtime dependencies. Node 18 or newer.
npm i @avvio/paymentsconst { PayoutsClient } = require('@avvio/payments');
const avvio = new PayoutsClient(); // also reads AVVIO_API_KEY and AVVIO_ORG_IDPaying someone
Section titled “Paying someone”const beneficiary = await avvio.createBeneficiary({ name: 'María González', country: 'MX', currency: 'MXN', endUserId: 'customer_42', // your id for the person sending externalId: 'cust42_maria', // makes a repeat create safe details: { clabeNumber: '012180000080004471' },});const destinationAccountId = beneficiary.method.destinationAccountId; // you pay thisIDEMPOTENCY_KEY=$(uuidgen) # new key per call; reuse it only to retry this exact requestcurl -s -X POST "$AVVIO_BASE_URL/recipients/$AVVIO_ORG_ID" \ -H "x-api-key: $AVVIO_API_KEY" \ -H "Idempotency-Key: $IDEMPOTENCY_KEY" \ -H "content-type: application/json" \ -d '{ "type": "individual", "name": "María González", "country": "MX", "externalId": "cust42_maria", "endUserId": "customer_42", "method": { "kind": "fiat", "currency": "MXN", "recipientDetails": { "clabeNumber": "012180000080004471" } } }' | tee response.jsonexport DESTINATION_ACCOUNT_ID=$(jq -r .method.destinationAccountId response.json)import osimport uuidimport requests
idempotency_key = str(uuid.uuid4()) # new key per call; reuse it only to retry this exact request
res = requests.post( f"{os.environ['AVVIO_BASE_URL']}/recipients/{os.environ['AVVIO_ORG_ID']}", headers={ "x-api-key": os.environ["AVVIO_API_KEY"], "Idempotency-Key": idempotency_key, }, json={ "type": "individual", "name": "María González", "country": "MX", "externalId": "cust42_maria", "endUserId": "customer_42", "method": { "kind": "fiat", "currency": "MXN", "recipientDetails": { "clabeNumber": "012180000080004471" } } },)print(res.status_code, res.json())os.environ["DESTINATION_ACCOUNT_ID"] = res.json()["method"]["destinationAccountId"] # a later step reads itavvio-payments beneficiary create \ --currency MXN --country MX \ --end-user customer_42 --external-id cust42_maria \ --field clabeNumber=012180000080004471 | tee response.jsonexport DESTINATION_ACCOUNT_ID=$(jq -r .method.destinationAccountId response.json)const idempotencyKey = crypto.randomUUID(); // persist it with the order so a retry reuses itconst payout = await avvio.payout({ amount: '200.00', destinationAccountId, expectDestination: quote.destinationAmount.amount, // 3384.65 endUser: { id: 'customer_42' }, reference: 'ZZ-2026-0042', idempotencyKey,});IDEMPOTENCY_KEY=$(uuidgen) # new key per call; reuse it only to retry this exact requestcurl -s -X POST "$AVVIO_BASE_URL/payments/organizations/$AVVIO_ORG_ID/payouts" \ -H "x-api-key: $AVVIO_API_KEY" \ -H "Idempotency-Key: $IDEMPOTENCY_KEY" \ -H "content-type: application/json" \ -d '{ "amount": "200.00", "destinationAccountId": "'"$DESTINATION_ACCOUNT_ID"'", "amountLeg": "source", "expectDestination": "3384.65", "maxDriftBps": 200, "reference": "ZZ-2026-0042", "endUser": { "id": "customer_42" } }'import osimport uuidimport requests
idempotency_key = str(uuid.uuid4()) # new key per call; reuse it only to retry this exact request
res = requests.post( f"{os.environ['AVVIO_BASE_URL']}/payments/organizations/{os.environ['AVVIO_ORG_ID']}/payouts", headers={ "x-api-key": os.environ["AVVIO_API_KEY"], "Idempotency-Key": idempotency_key, }, json={ "amount": "200.00", "destinationAccountId": os.environ["DESTINATION_ACCOUNT_ID"], "amountLeg": "source", "expectDestination": "3384.65", "maxDriftBps": 200, "reference": "ZZ-2026-0042", "endUser": { "id": "customer_42" } },)print(res.status_code, res.json())avvio-payments pay --amount 200.00 \ --to "$DESTINATION_ACCOUNT_ID" \ --expect 3384.65 \ --end-user customer_42 --reference ZZ-2026-0042Every method
Section titled “Every method”57 methods, parsed from the index.d.ts shipping with the package. eachEvent, eachPayout, eachAuditEvent and eachBalanceTransaction are async iterators that handle pagination for you.
constructor
Section titled “constructor”constructor(opts?: PayoutsClientOptions);corridors
Section titled “corridors”The currencies you can pay out to, and what this routing can do. capabilities.exactOutput says whether the locking amountLeg modes work.
corridors(currency?: string): Promise<{ corridors: Corridor[]; capabilities: { exactOutput: boolean; indicativePricing: boolean };}>;requirements
Section titled “requirements”requirements(currency: string): Promise<Corridor>;quote(args: { amount: string; to: string; from?: string;}): Promise<PreviewQuote>;createBeneficiary
Section titled “createBeneficiary”createBeneficiary(args: { name: string; currency: string; details: Record<string, string>; /** Optional contact address, kept for your records. Stored as null when omitted. */ email?: string; type?: 'individual' | 'business'; country?: string; /** Your id for the person sending. Scopes the beneficiary to them. */ endUserId?: string; /** Your id for this beneficiary. Makes a repeat create safe. */ externalId?: string; idempotencyKey?: string;}): Promise<Beneficiary>;listBeneficiaries
Section titled “listBeneficiaries”listBeneficiaries(args?: { endUserId?: string; /** 1 to 100; defaults to 50. */ limit?: number; /** The `nextCursor` from the previous page, unchanged. */ cursor?: string;}): Promise<{ /** 'organization' or the end user the list was scoped to. */ scope: string; recipients: Beneficiary[]; /** More rows follow; pass `nextCursor` back as `cursor`. */ hasMore?: boolean; nextCursor?: string | null;}>;getBeneficiary
Section titled “getBeneficiary”One beneficiary, by the id we returned. NOT_FOUND if it is not yours.
getBeneficiary(recipientId: string): Promise<Beneficiary>;getBeneficiaryByExternalId
Section titled “getBeneficiaryByExternalId”One beneficiary, by your id for them: the same externalId that makes creation idempotent. Unique per organization, so this is exactly one beneficiary or a NOT_FOUND, rather than paging your whole book.
getBeneficiaryByExternalId(externalId: string): Promise<Beneficiary>;updateBeneficiary
Section titled “updateBeneficiary”Correct a beneficiary’s own details. Only the fields you pass are changed. Bank details cannot be edited. The rail validated that account, and swapping it underneath would send the next payout somewhere you never registered. A wrong account is a new payment method, and the old one is deleted with deleteBeneficiaryMethod.
updateBeneficiary( recipientId: string, patch: { type?: 'individual' | 'business'; name?: string; email?: string; phone?: string; /** ISO-3166 alpha-2. */ country?: string; }, opts?: { idempotencyKey?: string },): Promise<Beneficiary>;deleteBeneficiary
Section titled “deleteBeneficiary”Remove a beneficiary and every payment method on it. Payouts already sent are history, not references; this cancels nothing in flight.
deleteBeneficiary( recipientId: string, opts?: { idempotencyKey?: string },): Promise<{ success: true }>;deleteBeneficiaryMethod
Section titled “deleteBeneficiaryMethod”Remove one way of paying a beneficiary, such as an account that closed, or one entered wrong. Returns the beneficiary without it; the destinationAccountId that method carried stops being payable.
deleteBeneficiaryMethod( recipientId: string, methodId: string, opts?: { idempotencyKey?: string },): Promise<Beneficiary>;getBeneficiaryMethodDetails
Section titled “getBeneficiaryMethodDetails”The full account details behind one payment method. Lists carry only last4. Deliberately loose: the fields are the corridor’s own, so they differ by rail exactly as requirements() does.
getBeneficiaryMethodDetails( recipientId: string, methodId: string,): Promise<Record<string, unknown>>;paymentReasons
Section titled “paymentReasons”The stated payment reasons this organization may use: the vocabulary purposeOfPayment on a payout, and paymentReason on a quote you accept, are validated against. Read it rather than guessing: a rejected value is a 400 on a payout you have already promised somebody.
paymentReasons(): Promise<{ payment_reasons: Record<string, unknown>[] }>;eachEvent
Section titled “eachEvent”Every event after a watermark, paged for you. since is exclusive, so resuming from a saved nextSince does not re-read it; still at-least-once (a crash before you save the watermark replays events). Dedupe on id.
eachEvent(args?: { since?: string; limit?: number; payoutId?: string; /** Event types to include; a string (`'payout.completed,payout.returned'`) or an array. */ type?: PayoutEventType | PayoutEventType[] | string;}): AsyncIterableIterator<PayoutEvent>;eachAuditEvent
Section titled “eachAuditEvent”Every audit event matching a filter, newest first, paged for you.
eachAuditEvent( args?: AuditEventFilters & { limit?: number; },): AsyncIterableIterator<AuditEvent>;eachPayout
Section titled “eachPayout”Every payout matching a filter, paged for you.
eachPayout(args?: { limit?: number; status?: string; endUserId?: string; reference?: string; updatedSince?: string;}): AsyncIterableIterator<Payout>;request
Section titled “request”The raw escape hatch: any method, any path, with auth and error handling applied. For an endpoint the typed methods do not cover yet.
request( method: string, path: string, opts?: { body?: unknown; query?: Record<string, unknown>; idempotencyKey?: string; headers?: Record<string, string>; },): Promise<unknown>;cancelPayout
Section titled “cancelPayout”Stop a payout that has not been funded yet. Once funded it cannot be canceled, and you get PAYOUT_NOT_CANCELABLE.
cancelPayout( payoutId: string, opts?: { idempotencyKey?: string },): Promise<Payout>;pricePayout
Section titled “pricePayout”The two halves of createPayout, exposed for callers that need to show a binding quote before committing.
pricePayout(args: { amount: string; destinationAccountId: string; purposeOfPayment?: string; // Deliberately loose: the snapshot is the rail's own quote object and its // shape varies by routing. Pass it straight back to `send()`.}): Promise<{ id: string; best_quote_id?: string; [k: string]: unknown }>;send(args: { snapshotId: string; quoteId?: string; endUser?: EndUser; reference?: string; idempotencyKey?: string;}): Promise<Payout>;payout
Section titled “payout”Price and send in one call, with the drift guard applied.
payout(args: PayoutArgs): Promise<Payout>;listEvents
Section titled “listEvents”Everything that has happened to your payouts, in order. The reconciliation primitive. Events are written once and never change, so carrying nextSince gives you exactly what is new, including a bank return that lands days after you booked the payout as settled, which listing payouts (ordered by creation) can never surface. At-least-once: dedupe on id.
listEvents(args?: { /** The `nextSince` from your last page. */ since?: string; limit?: number; /** Everything that ever happened to one payout. */ payoutId?: string; /** * Event types to include; a string (`'payout.completed,payout.returned'`) * or an array. Unknown values are a 400, never an empty page. */ type?: PayoutEventType | PayoutEventType[] | string;}): Promise<{ data: PayoutEvent[]; hasMore: boolean; nextSince: string | null;}>;listApprovals
Section titled “listApprovals”Payouts and batch runs waiting on your approvers. payout() and confirmPayoutBatch() answer 202 with a PendingApproval when the organization’s policy holds them; this is that queue. Approving and rejecting are dashboard actions. A key cannot take them, so there is no method.
listApprovals(args?: { status?: PayoutApprovalStatus; /** 1–100, default 50. */ limit?: number;}): Promise<{ data: PayoutApproval[] }>;getApproval
Section titled “getApproval”One approval, by the approvalId a 202 gave you.
getApproval(approvalId: string): Promise<PayoutApproval>;listAuditEvents
Section titled “listAuditEvents”A page of audit events, newest first: who did what, from where, with which credential. id is the cursor. A read-only key may read this.
listAuditEvents( args?: AuditEventFilters & { /** 1–100, default 50. */ limit?: number; /** A `nextCursor` we issued: rows strictly older than it. */ cursor?: string; },): Promise<{ data: AuditEvent[]; hasMore: boolean; nextCursor: string | null;}>;getFunding
Section titled “getFunding”How to fund a payout that came back with requiresFunding: true. Your funds stay in your wallet until you move them. A pure read; poll it freely.
getFunding(payoutId: string): Promise<{ payoutId: string; amount: string; currency: string; depositAddress: string; network: string; /** * Normally `null`. There is no countdown on the deposit address. The rail * decides when an unfunded payout is over and reports that as the payout's * own status, so read `getPayout()` before sending against instructions you * fetched a while ago; do not build a timer on it. */ expiresAt: string | null; instructions: string;}>;confirmFunding
Section titled “confirmFunding”Proof you sent the funds: the transaction hash you broadcast.
confirmFunding( payoutId: string, proof: { transactionHash: string; idempotencyKey?: string; },): Promise<Payout>;getPayout
Section titled “getPayout”Always live. More authoritative than a webhook you may have missed.
getPayout(payoutId: string): Promise<Payout>;listPayouts
Section titled “listPayouts”A page of payouts, not an array: read the rows from the page and follow its cursor.
listPayouts(args?: { limit?: number; cursor?: string; status?: string; endUserId?: string; reference?: string; /** * **The change feed.** Payouts whose state changed at or after this time, * oldest-changed first. * * Ordinary listing is newest-first by creation, which by construction can * never tell you an old payout changed, and `completed → failed` on a bank * return, days later, is exactly the change a ledger cannot afford to miss. * Page forward, carry the highest `updatedAt` you have seen as your * watermark, and you observe every revision at least once. * * `updatedSince` is inclusive, so resuming re-reads the row at your * watermark. Dedupe on payoutId + updatedAt. */ updatedSince?: string;}): Promise<{ data: Payout[]; hasMore: boolean; nextCursor: string | null }>;createPayoutBatch
Section titled “createPayoutBatch”Submit up to 1,000 payouts as one run. 202 means received, not paid: every line is validated first (nothing priced, nothing debited), then the valid lines become ordinary payouts, each with its own payoutId.
createPayoutBatch(args: PayoutBatchArgs): Promise<PayoutBatch>;listPayoutBatches
Section titled “listPayoutBatches”A page of batches, newest first, not an array.
listPayoutBatches(args?: { status?: PayoutBatchStatus; /** Exact match on your own run id. */ externalReferenceId?: string; /** 1–100, default 50. */ limit?: number; /** A `nextCursor` we issued. Anything else is a 400, never an empty page. */ cursor?: string;}): Promise<{ data: PayoutBatch[]; hasMore: boolean; nextCursor: string | null;}>;getPayoutBatch
Section titled “getPayoutBatch”One run, with its counts. Authoritative for the batch, not the payouts.
getPayoutBatch(batchId: string): Promise<PayoutBatch>;listPayoutBatchItems
Section titled “listPayoutBatchItems”The lines of a run, instruction echoed back verbatim. status: 'invalid' is the review screen after awaiting_confirmation; 'created' joins the run to the payout ledger. The CSV export (?format=csv) is a plain file download; this method returns the JSON page.
listPayoutBatchItems( batchId: string, args?: { status?: PayoutBatchItemStatus; /** 1–1,000, default 100. */ limit?: number; /** The `nextCursor` from your previous page: the last line index. */ cursor?: string; },): Promise<{ data: PayoutBatchItem[]; hasMore: boolean; nextCursor: string | null;}>;confirmPayoutBatch
Section titled “confirmPayoutBatch”Proceed with the valid lines of a held run. Only legal from awaiting_confirmation; anything else is PAYOUT_BATCH_NOT_CONFIRMABLE.
confirmPayoutBatch( batchId: string, opts?: { idempotencyKey?: string },): Promise<PayoutBatch>;cancelPayoutBatch
Section titled “cancelPayoutBatch”Stop a run before any payout exists. Once creation begins the run is committed and this is PAYOUT_BATCH_NOT_CANCELABLE. A payout the run created can be canceled with cancelPayout only while it waits on your own funding.
cancelPayoutBatch( batchId: string, opts?: { idempotencyKey?: string },): Promise<PayoutBatch>;fundingAccounts
Section titled “fundingAccounts”fundingAccounts(): Promise<unknown>;getPolicy
Section titled “getPolicy”What your organization is bound by, read live. Read it first: caps, approval threshold, features, rate limits, idempotency windows and the currencies that need a purposeOfPayment.
getPolicy(): Promise<Policy>;balance
Section titled “balance”Everything a payout can draw on (cached up to 60 s): what the payment network holds for you (provider) plus the USD stablecoins in your own wallet (wallet, per chain, at face value), summed per currency into balances with USD as the amount headline. Size payouts against that, knowing wallet figures are face value before the network and conversion cost of moving them onto the settling rail. A payout for exactly amount can fail to fund when the money must be gathered from several chains first. ledger is our own append-only record of the network-held part, with what holds currently reserve. Never summed with the others; a difference from provider is what listBalanceTransactions() explains. unavailable names any source that failed to read: when non-empty the figures are a floor, not the balance, so retry before concluding you cannot fund a payout.
balance(): Promise<{ currency: string; amount: string; balances: Money[]; provider: Money[]; /** Always empty in sandbox. */ wallet: WalletBalance[]; /** Sources that failed to read; non-empty results are not cached, so retry. */ unavailable: Array<'network' | 'wallet'>; ledger: LedgerBalance[];}>;listBalanceTransactions
Section titled “listBalanceTransactions”A page of balance transactions, newest first, not an array. Every change to what you can spend, each with balanceAfter; id is the cursor and the dedupe key. Served on every environment; an empty page means no rows yet, not an error.
listBalanceTransactions( args?: BalanceTransactionFilters & { /** 1–100, default 100. */ limit?: number; /** A `nextCursor` we issued: rows strictly older than it. */ cursor?: string; },): Promise<{ data: BalanceTransaction[]; hasMore: boolean; nextCursor: string | null;}>;eachBalanceTransaction
Section titled “eachBalanceTransaction”Every balance transaction matching a filter, newest first, paged for you.
eachBalanceTransaction( args?: BalanceTransactionFilters & { limit?: number; },): AsyncIterableIterator<BalanceTransaction>;Credit a sandbox balance. Test keys only.
fund(amount?: string, idempotencyKey?: string): Promise<{ balance: string }>;createPayoutLink
Section titled “createPayoutLink”Mint a one-time link for the person being paid, so they enter their own bank details and you never hold them. The token the recipient’s page needs is the part of url after /l/.
createPayoutLink(args: { amount: string; /** e.g. 'MXN'. `to` is accepted as an alias. */ destinationCurrency?: string; to?: string; endUserId: string; /** The sender (your end user). `email` is where the Reg E receipt goes. */ endUser?: { name?: string; email?: string }; reference?: string; /** Defaults to 60. Capped at 7 days. */ expiresInMinutes?: number; idempotencyKey?: string;}): Promise<{ payoutLinkId: string; url: string; expiresAt: string; status: string;}>;createCheckoutLink
Section titled “createCheckoutLink”Create a checkout link; with publish: true the response carries shareUrl. An idempotency key is minted when you do not pass one (the server requires it on every checkout write). A refused publish does not reject: the promise resolves with the kept draft, status: 'draft' and publishError set. Branch on link.status !== 'sent'.
createCheckoutLink(args: CreateCheckoutLinkArgs): Promise<CheckoutLink>;getCheckoutLink
Section titled “getCheckoutLink”getCheckoutLink(linkId: string): Promise<CheckoutLink>;listCheckoutLinks
Section titled “listCheckoutLinks”listCheckoutLinks(args?: { status?: CheckoutLinkStatus; /** `api`: made with an API key. `dashboard`: made by a person. Omit for both. */ source?: CheckoutLinkSource; limit?: number; cursor?: string;}): Promise<{ items: CheckoutLink[]; nextCursor?: string }>;updateCheckoutLink
Section titled “updateCheckoutLink”Edit a draft, or move a live link’s deadline. A sent link accepts expiresAt alone (an instant, or null to clear); anything else on a live link is a 400.
updateCheckoutLink( linkId: string, args: UpdateCheckoutLinkArgs,): Promise<CheckoutLink>;pauseCheckoutLink
Section titled “pauseCheckoutLink”Permanent: a paused link cannot be republished.
pauseCheckoutLink(linkId: string): Promise<CheckoutLink>;listCheckoutPayments
Section titled “listCheckoutPayments”Newest first. Base units with decimals; the webhook uses decimal strings.
listCheckoutPayments( linkId: string, args?: { limit?: number; cursor?: string },): Promise<{ items: CheckoutPayment[]; nextCursor?: string }>;refundCheckoutPayment
Section titled “refundCheckoutPayment”Moves money: refunds a card payment, whole (omit amount) or in part. The key needs the refunds scope. Retry a timeout with the same idempotencyKey. Resolves with the payment after the refund and the refund just made.
refundCheckoutPayment( paymentId: string, args?: RefundCheckoutPaymentArgs,): Promise<CheckoutPayment & { refund: CheckoutRefund | null }>;simulateCheckoutPayment
Section titled “simulateCheckoutPayment”Pay a sandbox link, as a buyer would. Test keys only. The link’s amount picks the outcome; listCheckoutSandboxScenarios() is the table.
simulateCheckoutPayment( linkId: string, p?: { reference?: string; clientReferenceId?: string; idempotencyKey?: string },): Promise<{ id: string; status: CheckoutPaymentStatus }>;simulateCheckoutPaymentOutcome
Section titled “simulateCheckoutPaymentOutcome”Force an outcome now instead of waiting out the timeline. Test keys only.
simulateCheckoutPaymentOutcome( paymentId: string, action: 'refund' | 'chargeback' | 'fail', idempotencyKey?: string,): Promise<{ id: string; status: CheckoutPaymentStatus; moved: boolean }>;listCheckoutSandboxScenarios
Section titled “listCheckoutSandboxScenarios”What each amount does in the sandbox. Test keys only.
listCheckoutSandboxScenarios(): Promise< { suffix: string; description: string }[]>;createProduct
Section titled “createProduct”createProduct(args: CreateProductArgs): Promise<CheckoutProduct>;listProducts
Section titled “listProducts”listProducts(): Promise<CheckoutProduct[]>;createWebhookEndpoint
Section titled “createWebhookEndpoint”Register a sandbox webhook endpoint. The secret is returned once and is not retrievable afterwards. Test keys only. Sends no Idempotency-Key and is never retried: a stored replay would keep the secret.
createWebhookEndpoint(args: { url: string; events?: string[] }): Promise<{ id: string; url: string; events: string[]; secret: string; warning: string;}>;webhookEndpoints
Section titled “webhookEndpoints”The webhook endpoints registered for your organization. Read-only by design, and there is no create/pause/delete counterpart here: a key that could repoint its own webhook URL could redirect every payout notification. The signing secret is never returned; it is shown once, at creation.
webhookEndpoints(): Promise< { id: string; url: string; events: string[]; /** Non-null while the endpoint is paused or auto-disabled. */ disabledAt: string | null; disabledReason: 'paused_by_owner' | 'auto_disabled_after_failures' | null; /** Retry ladders exhausted since the last accepted delivery. */ consecutiveFailures: number; lastSuccessAt: string | null; lastFailureAt: string | null; createdAt: string; }[]>;webhookEndpointDeliveries
Section titled “webhookEndpointDeliveries”The 50 most recent delivery attempts for one endpoint, newest first. No payloads. Replay from the dashboard if you need the body, which re-fires under the same eventId so your dedupe still holds.
webhookEndpointDeliveries(endpointId: string): Promise< { id: string; /** The event id: the `svix-id` we sent and the feed row's `id`. Dedupe on this. */ eventId: string; eventType: string; attempts: number; /** Null until an attempt is accepted. */ deliveredAt: string | null; /** When we will try again. Retries back off over roughly 70 hours. */ nextAttemptAt: string | null; /** Your server's response on the most recent failure. */ lastError: string | null; createdAt: string; }[]>;webhookDeliveries
Section titled “webhookDeliveries”What we sent, what came back, and what we retried. Sandbox endpoints.
webhookDeliveries(endpointId: string): Promise<unknown>;Was this page helpful?