Payments
Unified resource for customer receipts (flow: INCOMING) and vendor payments (flow: OUTGOING). Create the payment and allocate it to invoices / bills in a single call, or split the work across two calls if you prefer.
Resource shape
{
"payment_id": "e8bf7b71-7437-4248-9b3e-cbb4ec48e945",
"flow": "INCOMING", // INCOMING | OUTGOING
"payment_type": "REGULAR", // REGULAR | ADVANCE | REFUND
"contact_id": "50989551-…",
"contact_name": "Ice Tales Foods Pvt Ltd",
"date": "2026-05-19T00:00:00",
"amount": 11800.00,
"mode": "BANK_TRANSFER", // CASH | BANK_TRANSFER | CHEQUE | UPI | CARD | OTHER
"reference_number": "UTR-25051209",
"description": null,
"currency_code":"INR",
"exchange_rate":1.0,
"bank_charges": 0.0,
"tds_amount": 0.0,
"allocations": [
{
"allocation_id": "3052b92b-…",
"invoice_id": "72946802-…", // null for OUTGOING allocations
"bill_id": null, // null for INCOMING allocations
"amount": 11800.00
}
],
"created_time": "...",
"last_modified_time": "..."
}/paymentsList payments (cursor-paginated)AIBooks.payments.READFilter by ?flow=INCOMING or ?flow=OUTGOING to keep AR and AP traffic separate. Standard cursor pagination via ?cursor= and ?per_page=.
/payments/{payment_id}Get one payment with allocationsAIBooks.payments.READCreate — one-shot (recommended)
/paymentsRecord a payment and apply it in the same callAIBooks.payments.CREATEPass allocations[] to record the payment AND apply it to one or more invoices / bills in a single transaction. The journal entry posts immediately and the target document statuses update.
Required fields
| Field | Notes |
|---|---|
flow | INCOMING for customer receipts, OUTGOING for vendor payments. |
contact_id | Customer (INCOMING) or vendor (OUTGOING) id. |
date | Posting date, ISO 8601. |
amount | Total payment amount in currency_code. |
mode | CASH | BANK_TRANSFER | CHEQUE | UPI | CARD | OTHER. Default BANK_TRANSFER. |
Optional fields
| Field | Notes |
|---|---|
reference_number | UTR / cheque number / external reference. |
description | Free-text note shown on the journal entry. |
payment_type | REGULAR (default) | ADVANCE | REFUND. |
bank_account_id | Bank/Cash account that receives (INCOMING) or pays out (OUTGOING). Get the id from GET /accounts filtered to account_type in BANK/CASH. Omit on single-bank orgs (auto-picks the first BANK/CASH account). |
currency_code | ISO 4217. Default INR. |
exchange_rate | FX rate to base currency. Required when currency_code != INR. |
bank_charges | Forex / wire fees withheld by the bank. |
tds_amount | TDS withheld by the counterparty. |
allocations[] | One-shot allocations (see below). Omit to leave the payment unapplied / record an advance. |
Allocations array
Each entry must have exactly one of invoice_id (for INCOMING) or bill_id (for OUTGOING), plus an amount. The sum of allocation amounts may not exceed the payment amount.
Example — customer receipt against one invoice
POST /api/public/v1/payments
Authorization: Bearer aibk_pat_…
Content-Type: application/json
{
"flow": "INCOMING",
"contact_id": "50989551-…",
"date": "2026-05-19",
"amount": 11800,
"mode": "BANK_TRANSFER",
"bank_account_id": "177b15de-…", // HDFC current a/c
"reference_number": "UTR-25051209",
"allocations": [
{ "invoice_id": "72946802-…", "amount": 11800 }
]
}Example — split across two invoices
{
"flow": "INCOMING",
"contact_id": "50989551-…",
"date": "2026-05-19",
"amount": 15000,
"mode": "BANK_TRANSFER",
"allocations": [
{ "invoice_id": "<INV-A>", "amount": 11800 },
{ "invoice_id": "<INV-B>", "amount": 3200 }
]
}Example — vendor payment against a bill
{
"flow": "OUTGOING",
"contact_id": "<vendor-id>",
"date": "2026-05-19",
"amount": 50000,
"mode": "BANK_TRANSFER",
"allocations": [
{ "bill_id": "<bill-id>", "amount": 50000 }
]
}Validation
- Sum of
allocations[].amount>amount→400 validation.invalid_value - INCOMING with a
bill_idin any allocation →400 - OUTGOING with an
invoice_idin any allocation →400 - Allocation amount > target's outstanding balance →
400 - Contact / invoice / bill not in this org →
404
Create — two-step (advance receipts)
Omit allocations to record an unapplied receipt / advance. Apply it later — possibly across multiple invoices over time:
/payments/{payment_id}/applyApply unapplied amount to one invoice or billAIBooks.payments.UPDATEPOST /api/public/v1/payments/<payment_id>/apply
{
"invoice_id": "<invoice-id>",
"amount": 11800
}The apply endpoint takes one allocation per call. Call it repeatedly to split a receipt across multiple invoices/bills.
Journal entries
Journal entry posting is automatic and runs in the same transaction as the payment header:
- INCOMING:
Dr Bank/Cash/Cr Accounts Receivable - OUTGOING:
Dr Accounts Payable/Cr Bank/Cash - If
tds_amount > 0on INCOMING:Dr TDS Receivablefor the withheld portion. - If
bank_charges > 0:Dr Bank Charges (expense). - FX: an exchange gain/loss line is posted when the payment rate differs from the invoice/bill rate.
Webhooks emitted
PAYMENT_RECEIVEDon INCOMING create.PAYMENT_MADEon OUTGOING create.INVOICE_PAIDwhen an allocation clears the invoice balance to zero.BILL_PAIDwhen an allocation clears the bill balance to zero.
Update
/payments/{payment_id}Update a payment / receiptAIBooks.payments.UPDATEUpdate a payment's fields and re-post its journal entry in one call. Every body field is optional— only what you send is changed; the rest is left as-is. The journal entry is regenerated with the correct poster for the payment's direction (receipt vs vendor payment).
Updatable fields
| Field | Notes |
|---|---|
flow | INCOMING | OUTGOING. Direction used when re-posting the journal. Inferred from the payment's allocations when omitted — set it explicitly for an unallocated payment whose direction you're changing. |
contact_id | Move the payment to a different customer/vendor. Currency re-locks to the new contact. |
date | Posting date, ISO 8601. |
amount | New amount (> 0). Blocked with 409 if the payment is already applied — see below. |
mode | CASH | BANK_TRANSFER | CHEQUE | UPI | CARD | OTHER. |
reference_number | UTR / cheque number / external reference. |
description | Free-text note on the journal entry. |
bank_account_id | Move the receipt/payment to a different BANK/CASH account; the journal's bank leg re-posts to it. |
currency_code / exchange_rate / bank_charges | FX + fee fields, same semantics as create. |
Example — correct the amount and reference of an unapplied receipt
PUT /api/public/v1/payments/<payment_id>
Authorization: Bearer aibk_pat_…
Content-Type: application/json
{
"amount": 15000,
"reference_number": "UTR-25051299",
"description": "revised per remittance advice"
}Returns the full updated payment object (same shape as GET /payments/{id}), wrapped in the payment envelope.
Applied-payment guard
Changing amount while the payment is allocated to one or more invoices/bills returns:
409 Conflict
{
"code": "conflict.allocated",
"message": "This payment is applied to invoices/bills. Unapply it before changing the amount, then re-apply.",
"field": "amount"
}To re-amount an applied payment: delete its allocations (or the whole payment and recreate), update, then re-apply. Non-amount fields (date, reference, description, bank account, …) can still be updated while allocated.
Validation
amountchange on an allocated payment →409 conflict.allocatedbank_account_idnot a BANK/CASH account in this org →404 not_found.resource- Payment not in this org →
404
/payments/{payment_id}Delete (reverses journal + allocations)AIBooks.payments.DELETEDeleting a payment reverses the bank + AR/AP journal entries and restores the invoice / bill balance. Applied allocations are released and the target document status reverts to SENT / OPEN / PARTIALLY_PAID as appropriate.