Payables
Upload invoices as payables, complete and approve them, and pay several approved payables in one batch transaction
A payable is an invoice you intend to pay. You upload the invoice, complete the payee and payment details, and the payable becomes APPROVED. You then pay several approved payables together in one batch transaction.
Prerequisites
- Receivers with bank accounts (fiat payables) or wallets (crypto payables). See Setup, Receivers, Bank Accounts and Wallets.
- To pay a batch, everything listed in Transactions.
Types and Statuses
| Type | Paid to | Payment fields |
|---|---|---|
FIAT | A bank account | bankAccount (bank account _id). The currency comes from the bank account. |
CRYPTO | A wallet | wallet (wallet _id) and tokenIsoCode. |
| Status | Meaning |
|---|---|
DRAFT | Created. Details missing or not yet confirmed. |
APPROVED | Complete and ready to pay. Still editable. |
REJECTED | Declined, with a rejectReason. |
PAID | Paid. Locked. |
The lifecycle:
- Upload or create →
DRAFT. - Update with the full set of fields →
APPROVED. An update with only some fields moves it back toDRAFT. - Add it to a batch transaction → stays
APPROVED, now linked to a payment. - Accept the transaction →
PAID. This happens at accept, not when the payout settles.
If you delete the batch transaction, its payables return to APPROVED and are unlinked. If a payable's payment has already failed when the transaction is accepted, that payable returns to APPROVED.
Step 1: Upload the Invoice
POST /payables/upload, multipart/form-data, with one file in the field file.
| Rule | Value |
|---|---|
| File types | PDF, PNG, JPEG, TIFF, WebP, GIF, HEIC, HEIF |
| Maximum size | 14.25 MiB |
Skyline reads the invoice and creates a DRAFT payable. The response is the payable, with:
nametaken from the file name,payableInfoholding the details read from the document (payee name, address, amount, bank or wallet details, reason),amountandtypefilled in when the document shows them.
payableInfo is a suggestion only. It is never used to pay. If nothing could be read, the payable is still created, with empty details.
| Status | Message | Cause |
|---|---|---|
| 400 | File type … is not allowed. Accepted: … | Unsupported file type. |
| 413 | File too large | Over the size limit. |
| 400 | Request has unsupported document format | The document could not be read. |
Creating a Payable Without Extraction
If you already hold the payment details, upload the file with POST /file/upload (multipart, field file). It returns fileKey. Then call POST /payables:
{
"documentS3Key": "Jane-Doe-6650aa11bb22cc33dd44ee55/invoice-1042-1727512345678",
"name": "Invoice 1042",
"type": "FIAT",
"receiver": "6650b1c2d3e4f5a6b7c8d9e0",
"bankAccount": "6650b2e3d4f5a6b7c8d9e0f1",
"amount": "25000.00",
"reason": "Invoice 1042"
}| Field | Required | Description |
|---|---|---|
documentS3Key | Yes | The fileKey from POST /file/upload. Without it the call fails with 500, not 400. |
name | No | A label. Defaults to the file name. |
type | No | FIAT or CRYPTO. |
receiver | No | The receiver _id. |
bankAccount | For FIAT | The receiver's bank account _id. |
wallet | For CRYPTO | The receiver's wallet _id. |
tokenIsoCode | For CRYPTO | The token to pay in. Unknown values return 403 Token not supported. |
amount | No | The amount to pay, as a decimal string. Fiat: in the bank account's currency. Crypto: in the token. |
reason | No | The payment reference. Same rules as a transaction payment reason. |
bankingFeesOption | No | OUR (default), SHA or BEN. |
A created payable is always DRAFT, even when every field is present. Approve it with an update.
Step 2: Complete and Approve
PATCH /payables/:id with the full set of fields for the payable's type. The server sets APPROVED only when the request body itself contains all of them. It does not merge with what is stored.
| Type | Fields the body must contain |
|---|---|
FIAT | type, name, amount, receiver, reason, bankAccount |
CRYPTO | type, name, amount, receiver, reason, wallet, tokenIsoCode |
{
"type": "FIAT",
"name": "Invoice 1042",
"amount": "25000.00",
"receiver": "6650b1c2d3e4f5a6b7c8d9e0",
"bankAccount": "6650b2e3d4f5a6b7c8d9e0f1",
"reason": "Invoice 1042"
}- Any body missing one of these fields moves the payable to
DRAFT, even if it wasAPPROVED. Always send the full set. - Read
statusin the response to confirmAPPROVED. - For
FIAT,reasonis checked against the bank account's currency. GBP: 1 to 18 characters,^[a-zA-Z0-9\s,.\-]{1,18}$. Other currencies: 2 to 140 characters,^[+/?:,.\s()a-zA-Z0-9]{2,140}$, no hyphens. - Check that
amountis greater than zero. The server does not, but a batch cannot pay a zero amount.
| Status | Message | Cause |
|---|---|---|
| 404 | No payable found. | Unknown id. |
| 403 | Must be the owner to update this payable | The payable belongs to another organisation. |
| 400 | Already Paid payables cannot be updated | The payable is PAID. |
| 400 | Reason must contain between 2 and 140 characters… | Invalid reason. |
| 403 | Token not supported | Unknown tokenIsoCode. |
Rejecting a Payable
POST /payables/:id/reject
{ "rejectReason": "Duplicate invoice" }The payable moves to REJECTED. A PAID payable cannot be rejected.
Reading Payables
GET /payables/me lists your organisation's payables. It returns results, page_total and total. When a payable is in a transaction, payment.transaction shows that transaction's _id, status, tokensToSend and token.
| Query parameter | Description |
|---|---|
limit | Page size. Default 20, maximum 250. |
offset | Number of results to skip. Default 0. |
status | One or more statuses, comma-separated. |
type | FIAT or CRYPTO. |
searchTerm | Free-text search over payee names, payable name, token, currency and account number. |
tokens, currencies | Filter by token or currency name, comma-separated. |
from, to | Creation date range. |
sortBy | createdAt or status. Default: newest first. |
direction | 1 ascending or -1 descending. Applies with sortBy. |
Other reads:
GET /payables/:idreturns one payable. 404Payable not foundfor an unknown id.GET /payables/countsreturns the number of payables per status:DRAFT,APPROVED,REJECTED,PAID.GET /file?key=<documentS3Key>returns a temporary URL to download the invoice.
Step 3: Pay a Batch
Create the Batch Transaction
POST /transactions/payables
{
"payableIds": ["6651b1c2d3e4f5a6b7c8d9e0", "6651b2c3d4e5f6a7b8c9d0e1"],
"token": "USDC"
}Every payable must be APPROVED, not already in a transaction, and of the same type.
| Status | Message |
|---|---|
| 400 | Please ensure that every payable is approved state |
| 400 | Please ensure that every payable is not already in a transaction |
| 400 | Please ensure that every payable is from the same type |
The response is a DRAFT transaction of type FIAT or CRYPTO, with one payment per payable. It has no network and no sending wallet yet.
Add the Network and Wallet
PUT /transactions/:id with the network, your organisation wallet, the token and the full payments array. Every payment must carry its payableId. The update replaces all payments. A payment sent without payableId leaves its payable linked to a deleted payment, and that payable can then never be paid or batched again.
Build each payment from its payable:
| Payment field | Fiat payable | Crypto payable |
|---|---|---|
type | FIAT | CRYPTO |
payableId | payable _id | payable _id |
bankAccount.id | payable bankAccount._id | — |
currencyIsoCode | payable bankAccount.currency.isoCode | — |
wallet | — | payable wallet._id |
tokenIsoCode | — | payable token.isoCode |
amount | payable amount | payable amount |
reason | payable reason | payable reason |
bankingFeesOption | payable bankingFeesOption | — |
documentS3Key | payable documentS3Key | payable documentS3Key |
{
"token": "USDC",
"network": "6650a1f2c3d4e5f6a7b8c9d0",
"walletAddress": "0x4b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c",
"payments": [
{
"type": "FIAT",
"payableId": "6651b1c2d3e4f5a6b7c8d9e0",
"bankAccount": { "id": "6650b2e3d4f5a6b7c8d9e0f1" },
"currencyIsoCode": "EUR",
"amount": "25000.00",
"reason": "Invoice 1042",
"bankingFeesOption": "OUR",
"documentS3Key": "Jane-Doe-6650aa11bb22cc33dd44ee55/invoice-1042-1727512345678"
},
{
"type": "FIAT",
"payableId": "6651b2c3d4e5f6a7b8c9d0e1",
"bankAccount": { "id": "6650b3f4a5b6c7d8e9f0a1b2" },
"currencyIsoCode": "GBP",
"amount": "4200.00",
"reason": "INV 2231",
"bankingFeesOption": "OUR",
"documentS3Key": "Jane-Doe-6650aa11bb22cc33dd44ee55/invoice-2231-1727512399999"
}
]
}Validate, Submit and Accept
Continue exactly as for a single transaction:
- Fiat payables: Crypto-to-Fiat from step 4: validate, check the allowance, approve, submit for a quote, and accept before the quote expires.
- Crypto payables: Crypto-to-Crypto from step 3, ending with the on-chain batch payment.
The payables become PAID when the transaction is accepted, or for crypto when the on-chain batch payment is confirmed.
Endpoint Reference
| Endpoint | Method | Purpose |
|---|---|---|
| /payables/upload | POST | Upload an invoice and create a draft payable |
| /file/upload | POST | Upload a file and get its key |
| /payables | POST | Create a payable from an uploaded file |
| /payables/:id | PATCH | Complete and approve a payable |
| /payables/:id/reject | POST | Reject a payable |
| /payables/me | GET | List payables |
| /payables/:id | GET | Read one payable |
| /payables/counts | GET | Count payables per status |
| /file | GET | Get a download URL for the invoice |
| /transactions/payables | POST | Create a batch transaction from approved payables |
| /transactions/:id | PUT | Add the network, wallet and payments to the batch |