Payment Requests
Review the reimbursement and invoice requests your payees submit, then approve, reject, or approve and pay them.
A payment request is a payee asking you to pay them: a reimbursement with receipts, or an invoice. The payee writes and submits it from the payee portal or the payee widget; you review it and decide. Approving and paying turns requests into ordinary payout payments, so everything in Payouts applies to the money once it moves.
Before you start
- Payment requests are enabled per account by Payment Labs.
- Every operation on this page needs the Payment Requests permission. See Access to wallets and payment requests.
- To pay requests you need a ledger account (see Funding and Ledger Accounts) and, optionally, a batch to pay into.
Request statuses
status | Meaning | Who moves it next |
|---|---|---|
DRAFT | The payee is still writing it. You never see drafts. | Payee submits. |
PENDING_APPROVAL | Submitted and waiting for your decision. | You approve or reject; the payee can cancel. |
APPROVED | You approved it. It has a payment once you pay it with Approve and Pay, which you can also use in one step from PENDING_APPROVAL. | You raise its payment with Approve and Pay. |
NOT_APPROVED | You rejected it with a reason. | The payee can correct it and submit again, which returns it to PENDING_APPROVAL. |
CANCELLED | The payee withdrew it. | Final. |
A request also lists the payments raised for it in payments, each with its paymentStatus, which follows the payment through its statuses.
Payment Labs sends no webhook when a payee submits or changes a request. Search the queue on a schedule to find new requests. Once you pay a request, its payment sends the usual payment webhooks.
Step 1: Find requests waiting for you
Search the queue with Search Payment Requests:
{
"filter": { "all": [{ "fact": "status", "operator": "equals", "value": "PENDING_APPROVAL" }] },
"sort": [{ "fieldName": "submittedAt", "order": "asc" }],
"page": 1,
"pageSize": 50
}You can filter and sort on the request's ID, type (REIMBURSEMENT or INVOICE), status, title, amount, currencyCode, invoice fields, requester.*, payments.*, and its dates. Search results can be a few seconds behind.
To page through every request instead, use List Received Payment Requests with limit (1 to 100, default 50) and pass the response's nextCursor as cursor for the next page.
Step 2: Read the request before you decide
Search is right for a list but wrong for a decision: the payee may have changed the request since it was indexed. Read it with Get Received Payment Request before you approve.
Each attached document is read automatically. extractionStatus on the attachment says whether reading is PENDING, COMPLETED or FAILED. Fields the reading was unsure of and the payee has not confirmed are listed in fieldsNeedingConfirmation (lines carry needsConfirmation), lineItemsDiscrepancy flags lines that do not add up to the document's total, and overrides shows every value the payee changed beside what was read.
To open an attached receipt or invoice, call Get Received Payment Request Attachment URL. It returns a previewUrl (to show in a browser), a downloadUrl, the fileName and an expiresAt. The links are short-lived; request new ones when they expire.
Step 3: Decide
You have three options. Each takes 1 to 50 requests per call except Reject, which takes one.
Reject
Reject Payment Request with a reason the payee will read:
{ "reason": "The receipt for the hotel night is missing. Please attach it and resubmit." }The request moves to NOT_APPROVED. Only a PENDING_APPROVAL request can be rejected.
Approve without paying
Approve Payment Requests records the decision only. No batch, ledger account or payment is involved. Use it when approval and payment are separate steps for you.
{ "requestIds": ["PR-01JDB1C2D3E4F5G6H7J8K9M0N1", "PR-01JDB1C2D3E4F5G6H7J8K9M0N2"] }Approve and pay
Approve Payment Requests and Pay approves the requests and raises their payments in one call. It accepts requests that are PENDING_APPROVAL or already APPROVED.
{
"requestIds": ["PR-01JDB1C2D3E4F5G6H7J8K9M0N1", "PR-01JDB1C2D3E4F5G6H7J8K9M0N2"],
"batch": { "name": "October reimbursements", "defaultPaymentCategory": "reimbursement" },
"ledgerAccount": "A-01JD8ZK3V6M2Q4R7T9W1X3Y5Z7",
"sendImmediately": true
}| Field | Description |
|---|---|
requestIds | 1 to 50 request IDs, no duplicates. Treat request IDs as opaque strings. |
batch | Either { "batchId": "B-..." } for an existing batch, or the fields to create one (name and defaultPaymentCategory required; referenceId and taxJurisdiction optional). Not both. |
ledgerAccount | The ID of the ledger account that pays. Its currency must match the requests' currency. |
sendImmediately | true submits the payments with auto-submit as they are created: they are sent now if the payee is payable, or as soon as they become payable. false (default) leaves them as DRAFT in the batch for you to submit. |
amountOverride, fundingCurrencyCode | Send both to pay a different amount than the requests state, for example when a request is in a currency your ledger account does not hold. amountOverride is a decimal in fundingCurrencyCode, which must be the ledger account's currency. Only allowed when all the requests belong to one payee. |
payorMemo, payeeMemo | Memos for the payments. Leave payeeMemo out to derive it from the request. |
How it behaves:
- One payment per payee. Several requests for the same payee are paid as one payment and share a
paymentId.createdCountcounts requests;createdPaymentCountcounts payments. - Retries are safe. Calling again with the same requests lands on the same payments; it never pays twice.
- A row can finish later. A row with
reservedPaymentIdand nopaymentIdis still having its payment created;queuedReasonsays why (BUDGET: the call ran out of time,TRANSIENT: a temporary error, retried automatically). Treat it as approved and not yet paid, and read the request later.
Read the results
Approve and Approve and Pay return 200 even when some requests were refused. Check every row in results:
{
"total": 2,
"approvedCount": 1,
"failedCount": 1,
"results": [
{ "requestId": "PR-01JDB1C2D3E4F5G6H7J8K9M0N1", "approved": true, "status": "APPROVED", "batchId": "B-01JDB2D3E4F5G6H7J8K9M0N1P2", "paymentId": "P-01JDB3E4F5G6H7J8K9M0N1P2Q3" },
{ "requestId": "PR-01JDB1C2D3E4F5G6H7J8K9M0N2", "approved": false, "errorCode": "NOT_AWAITING_DECISION" }
],
"batchId": "B-01JDB2D3E4F5G6H7J8K9M0N1P2",
"batchCreated": true,
"createdCount": 1,
"createdPaymentCount": 1,
"queuedCount": 0,
"queuedPaymentCount": 0
}Row errorCode | Meaning |
|---|---|
NOT_FOUND | No such request on your account. |
NOT_AWAITING_DECISION | The request is not in a state you can decide (for example it was cancelled or rejected). |
ZERO_AMOUNT | The request is worth nothing. |
AMOUNT_NOT_DETERMINABLE | The request has no single total yet, or its documents are in more than one currency. Use amountOverride. |
MIXED_CURRENCY_FOR_PAYEE | Requests for one payee are in different currencies, so they cannot be one payment. |
ALREADY_PAID | A payment already settles this request; paymentId names it. |
REQUEST_NO_LONGER_PAYABLE | The request changed and can no longer be paid as approved. |
RESERVED_FOR_ANOTHER_BATCH, RESERVED_FOR_ANOTHER_SET | The request is already being paid in a different batch or together with different requests. Repeat the original call instead. |
PAYMENT_CREATION_REFUSED, PAYMENT_NOT_CREATED | The payment was refused, for example because the ledger account's currency does not match. message has details. Other payment error codes can also appear here. |
PAYMENT_CREATION_UNAVAILABLE, PAYMENT_NOT_QUEUED, APPROVAL_NOT_RECORDED | A temporary failure. Retry the call. |
NOT_APPROVED | The request was not approved for another reason. |
Errors and edge cases
| Error | Operation | Meaning |
|---|---|---|
400 INVALID_REQUEST_IDS | Approve, Approve and Pay | requestIds is missing, empty, over 50, or has duplicates. |
400 INVALID_LEDGER_ACCOUNT | Approve and Pay | ledgerAccount is missing or blank. |
400 INVALID_BATCH | Approve and Pay | batch names both an existing and a new batch, or neither. |
400 INVALID_AMOUNT_OVERRIDE, 400 INVALID_FUNDING_CURRENCY | Approve and Pay | amountOverride is not a valid amount, or fundingCurrencyCode is missing or not a 3-letter code. |
400 AMOUNT_OVERRIDE_AMBIGUOUS | Approve and Pay | amountOverride was sent for requests from more than one payee. |
400 INVALID_SEND_IMMEDIATELY, 400 INVALID_PAYOR_MEMO, 400 INVALID_PAYEE_MEMO | Approve and Pay | The field has the wrong type. |
400 BATCH_CREATION_FAILED | Approve and Pay | The new batch could not be created. Nothing was paid. |
404 NOT_FOUND | Approve and Pay, Get, Reject | The batch or request does not exist on your account. |
423 NOT_EDITABLE | Approve and Pay | The batch is archived. Nothing was paid. |
400 INVALID_REJECTION_REASON | Reject | reason is missing, blank or longer than 2,000 characters. |
423 NOT_EDITABLE | Reject | The request is not PENDING_APPROVAL. |
400 INVALID_LIMIT | List | limit is not between 1 and 100. |
Next steps
Updated 1 day ago
What’s Next
Next: Wallets Overview.
