Last updated

Core concepts

The entities of the Payment API and how they relate.

Five entities carry the whole model: Company, Funding accounts (product accounts), Beneficiaries (with their payment methods), Quotes, and Payments.

Company (you)
 ├── Funding accounts         what you pay FROM (productAccounts)
 │     self-funded · credit-line · crypto-wallet · jeeves-pay-*
 │
 ├── Beneficiaries           who you pay
 │     └── Payment methods   HOW each one is paid (spei, wire, ach, swift, …)
 │
 ├── Quotes                  a locked FX rate for one payment (single-use, expiring)
 │
 └── Payments                money in motion: quote + account + beneficiary
       └── status history    pushed as payment.status.updated webhooks

Company

Your company at Jeeves. Credentials belong to a company; GET /v1/product-accounts returns an array of companies, one entry per company the credential covers — a single-company integration reads [0].productAccounts.

Funding account (product account)

The account a payment is funded from — productAccount in the API. You can hold several, across currencies and types (a USD fiat balance, a stablecoin wallet such as USDC, an MXN balance, credit in USD/MXN/BRL, …). Each has a type — self-funded (your pre-funded balance), credit-line (Jeeves credit; also what you send for a Bill Pay credit account, which GET /v1/product-accounts reports as jeeves-pay-credit), or crypto-wallet (stablecoin balance, currency reported as a ticker like USDC) — a currency, and a status. Only active accounts can fund a payment, and the account's currency must match the quote's sourceCurrency. Your FX rate and fees depend on the type, and the type shapes which purpose codes a corridor accepts.

Beneficiary

A person (individual) or business (business) you pay. Individuals carry firstName/lastName; businesses carry name. Each beneficiary has a bank country and currency, contact information, and zero or more attached payment methods (spei, wire, ach, swift, …). You reference both on a payment: beneficiaryId plus beneficiaryPaymentMethodId. Deleted beneficiaries and methods are never returned by the API. You can read one beneficiary with GET /v1/beneficiaries/{beneficiaryId} and create one with POST /v1/beneficiaries — see Beneficiaries.

Payment method

How one beneficiary is paid: a rail such as spei or wire, with the account details that rail needs. A beneficiary holds at most one payment method per rail. GET /v1/beneficiary-schemas tells you which methods a corridor offers and which bankingInformation and contactInformation fields each needs, and POST /v1/payment-methods attaches one. The paymentMethodId it returns is the beneficiaryPaymentMethodId you send on a payment.

Quote

A locked FX rate for one payment. Quotes are destination-amount based: you say what the beneficiary must receive, and the source amount is derived from the rate. A quote is single-use and expires — read expiresAt from the response (the TTL is configured per company; null means it doesn't expire). Reusing a consumed quote returns 422 quote_expired.

Corridor

The combination of source currency and account type on one side and destination currency/country on the other. A corridor can be domestic — same currency and country on both sides (e.g. MXN → MXN to a Mexican business via SPEI) — and the flow is identical. The corridor decides which purpose codes are valid and what beneficiary details are required. See the purpose-code table in Send a payment.

Purpose code

A regulatory declaration of why the payment is being made — always a string identifier like purchase_of_services, never a number. Mandatory on every payment, with no default. Each corridor accepts a fixed set of identifiers, listed in purposeCodes[] by GET /v1/beneficiary-schemas (see Send a payment); omitting it is a 400, and an identifier outside the corridor's set is 422 validation_failed.

Idempotency key

A customer-generated UUID sent as the Idempotency-Key header on POST /v1/payments, unique per payment attempt and never expiring. Same key + same body replays the original result byte for byte; same key + different body returns 409 idempotency_conflict. On timeout or 5xx, retry with the same key — a fresh key on retry is how duplicate payments happen.

Requote

What happens when onQuoteExpiry: requote rescues an expired quote: a new quote is priced at the current rate and used. The destination amount is unchanged; the source amount moves. The response then carries both quoteId (the quote that priced the payment) and originalQuoteId (the expired one you sent) — the one case where you pay a rate you were not previously shown.

Credential vs. scope

Your credential is the client ID + secret pair and the client certificate issued by Jeeves — it identifies your company. Scopes are the permissions you request on each token (payments:write, quotes:write, …); a token can never reach a capability outside its scopes. beneficiaries:write covers creating beneficiaries and attaching payment methods, and payments:write covers creating and cancelling payments. Some scopes (the *:multiple:* family, cards:multiple:write) are enabled on a credential only on request — ask Jeeves support.

Cancel

Stopping a payment you no longer want to send. You can cancel only while the status is in-review or scheduled; the payment becomes canceled, a canceled webhook is sent, and the quote stays spent. The call isn't idempotent. POST /v1/payments/{paymentId}/cancel — see Cancelling a payment.

Terminal status

A status a payment can never leave: completed, rejected, failed, canceled, refunded. Everything else (accepted, in-review, scheduled, in-progress) is in flight. failureReason is populated for rejected, failed, and canceled.