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

Request statuses

statusMeaningWho moves it next
DRAFTThe payee is still writing it. You never see drafts.Payee submits.
PENDING_APPROVALSubmitted and waiting for your decision.You approve or reject; the payee can cancel.
APPROVEDYou 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_APPROVEDYou rejected it with a reason.The payee can correct it and submit again, which returns it to PENDING_APPROVAL.
CANCELLEDThe 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
}
FieldDescription
requestIds1 to 50 request IDs, no duplicates. Treat request IDs as opaque strings.
batchEither { "batchId": "B-..." } for an existing batch, or the fields to create one (name and defaultPaymentCategory required; referenceId and taxJurisdiction optional). Not both.
ledgerAccountThe ID of the ledger account that pays. Its currency must match the requests' currency.
sendImmediatelytrue 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, fundingCurrencyCodeSend 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, payeeMemoMemos 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. createdCount counts requests; createdPaymentCount counts 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 reservedPaymentId and no paymentId is still having its payment created; queuedReason says 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 errorCodeMeaning
NOT_FOUNDNo such request on your account.
NOT_AWAITING_DECISIONThe request is not in a state you can decide (for example it was cancelled or rejected).
ZERO_AMOUNTThe request is worth nothing.
AMOUNT_NOT_DETERMINABLEThe request has no single total yet, or its documents are in more than one currency. Use amountOverride.
MIXED_CURRENCY_FOR_PAYEERequests for one payee are in different currencies, so they cannot be one payment.
ALREADY_PAIDA payment already settles this request; paymentId names it.
REQUEST_NO_LONGER_PAYABLEThe request changed and can no longer be paid as approved.
RESERVED_FOR_ANOTHER_BATCH, RESERVED_FOR_ANOTHER_SETThe request is already being paid in a different batch or together with different requests. Repeat the original call instead.
PAYMENT_CREATION_REFUSED, PAYMENT_NOT_CREATEDThe 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_RECORDEDA temporary failure. Retry the call.
NOT_APPROVEDThe request was not approved for another reason.

Errors and edge cases

ErrorOperationMeaning
400 INVALID_REQUEST_IDSApprove, Approve and PayrequestIds is missing, empty, over 50, or has duplicates.
400 INVALID_LEDGER_ACCOUNTApprove and PayledgerAccount is missing or blank.
400 INVALID_BATCHApprove and Paybatch names both an existing and a new batch, or neither.
400 INVALID_AMOUNT_OVERRIDE, 400 INVALID_FUNDING_CURRENCYApprove and PayamountOverride is not a valid amount, or fundingCurrencyCode is missing or not a 3-letter code.
400 AMOUNT_OVERRIDE_AMBIGUOUSApprove and PayamountOverride was sent for requests from more than one payee.
400 INVALID_SEND_IMMEDIATELY, 400 INVALID_PAYOR_MEMO, 400 INVALID_PAYEE_MEMOApprove and PayThe field has the wrong type.
400 BATCH_CREATION_FAILEDApprove and PayThe new batch could not be created. Nothing was paid.
404 NOT_FOUNDApprove and Pay, Get, RejectThe batch or request does not exist on your account.
423 NOT_EDITABLEApprove and PayThe batch is archived. Nothing was paid.
400 INVALID_REJECTION_REASONRejectreason is missing, blank or longer than 2,000 characters.
423 NOT_EDITABLERejectThe request is not PENDING_APPROVAL.
400 INVALID_LIMITListlimit is not between 1 and 100.

Next steps


What’s Next

Did this page help you?