---
title: "Sales & Receivables"
description: "Contacts, products, quotations, sales orders, invoices, credit and debit notes."
url: "https://bareledger.app/guides/api-sales"
category: "Developers & API"
updated: "2026-10-23"
publisher: "BareLedger (Syneria Technologies OPC)"
---

# Sales & Receivables

> Contacts, products, quotations, sales orders, invoices, credit and debit notes.

## Directory (shared master data): Contacts, Addresses, Products

Customers & Suppliers is shared by Sales, Purchases, Collections and Compliance and sits in the **Directory** sidebar group, not under Sales. The contact and address endpoints carry **no permission gate**: every active member of the tenant may read and write them (authentication and tenant scope still apply; read-only API keys are still refused on writes).

| Method & Path | Permission | Purpose |
|---|---|---|
| `GET/POST contacts`, `PATCH contacts/{contact}` | any member | Customer/supplier master |
| `GET contacts/{contact}/suggested-tax-rates` | any member | Place-of-supply tax suggestion (Canadian provinces; empty elsewhere) |
| `GET/POST contacts/{contact}/addresses`, `PUT/DELETE .../{address}` | any member | Multi-address per contact |
| `POST contacts/{contact}/addresses/copy-billing-to-shipping` | any member | Clone default billing → shipping |
| `POST .../addresses/{address}/set-default-billing` \| `/set-default-shipping` | any member | Set default address |
| `GET/POST products`, `GET products/{product}`, `PUT products/{product}` | `product.view`/`product.manage` | Catalogue |
| `GET products/export` / `POST products/import` | `product.export`/`product.import` | CSV in/out (all-or-nothing import) |
| `POST products/{product}/activate` \| `/deactivate` \| `/duplicate` | `product.manage` | Lifecycle actions |
| `GET/POST product-categories`, `PUT/DELETE .../{category}` | `product.view`/`product.manage` | Hierarchical categories |

## Quotations & Sales Orders

Identical route shape registered twice — `quotations` and `sales-orders` — with one difference: a sales order can only convert to an invoice (`target_kind=invoice`).

| Method & Path (`{path}` = quotations \| sales-orders) | Permission | Purpose | Side effects |
|---|---|---|---|
| `GET {path}` / `GET {path}/{document}` | `sales.view` | List / detail | — |
| `POST {path}` | `sales.manage` | Create draft | No GL posting for these kinds |
| `PUT {path}/{document}` / `DELETE {path}/{document}` | `sales.manage` | Edit / delete draft | — |
| `GET {path}/{document}/pdf` / `/history` | `sales.view` | PDF / lifecycle history | — |
| `POST {path}/{document}/send` \| `/accept` \| `/decline` \| `/expire` | `sales.manage` | Lifecycle status | — |
| `POST {path}/{document}/convert` | `sales.manage` | Convert to sales order/invoice | Creates a new linked `CommercialDocument` |

## Invoices

| Method & Path | Permission | Feature gate | Purpose | Side effects |
|---|---|---|---|---|
| `GET invoices` / `GET invoices/{document}` | `sales.view` | — | List / detail | — |
| `POST invoices` | `sales.manage` | — | Create draft | Optional `captured_payment` object (point-of-sale capture, see below) |
| `PUT invoices/{document}` / `DELETE invoices/{document}` | `sales.manage` | — | Edit/delete draft | `PUT` accepts the same optional `captured_payment` |
| `GET invoices/{document}/pdf` / `/history` / `/payments` | `sales.view` | — | PDF / history / settlement summary | — |
| `POST invoices/{document}/submit` \| `/review` \| `/approve` \| `/reject` \| `/reverse` | `sales.submit/review/approve/reject/reverse` | — | Workflow transitions | `approve` **posts to ledger** (402 `usage_limit_reached` at the monthly transaction cap); `reverse` **submits a reversing entry for approval** |
| `GET/POST invoices/{document}/einvoice`, `POST .../einvoice/submit` | `sales.view`/`sales.manage` | `feature:e_invoicing` | Country e-invoice (ZATCA/PEPPOL/PH EIS/JP) | External transport call (no-op `NullTransport` by default) |
| `GET/POST invoices/{document}/attachments` | `sales.view`/`sales.manage` | — | Attachments | Stores file (10MB cap), audit |
| `GET invoices/{document}/deliveries` | `sales.view` | — | Email delivery history | — |
| `POST invoices/{document}/send` | `sales.manage` | — | Email invoice PDF | **Sends email**, records `InvoiceDelivery` |
| `POST invoices/{document}/resend/{delivery}` | `sales.manage` | — | Resend using a prior delivery's recipient/cc | **Sends email** |

### Point-of-sale capture (`captured_payment`)

The former one-step `POST invoices/cash-sale` endpoint (and the tenant opt-in `allow_cash_sale_direct_post`) has been **removed**. For a sale paid at the counter, send an optional `captured_payment` object on `POST`/`PUT invoices`: `cash_account_id` (required — a cash/bank GL account), `payment_method` (default `cash`; `check_number` required for `check`), `reference`, `check_number`, `gateway_transaction_id`, `bank_account_id`. It is validated at capture and returned on the invoice resource. Nothing posts at capture: the invoice goes through its normal review/approval; when its approval posts it, the same transaction drafts the payment with the **invoice creator** as preparer, allocated to the invoice, and submits it for its own review and approval. The invoice is settled when that payment posts. Each posting consumes its own unit of the monthly transaction cap.

## Customer credits

| Method & Path | Permission | Purpose | Side effects |
|---|---|---|---|
| `GET customer-credits` / `GET customer-credits/{credit}` | `payment.view` | List / detail | Each row carries `pending_request` (the open apply/refund sign-off, or `null`) |
| `POST customer-credits/{credit}/apply` | `payment.manage` | Request applying the credit to a posted invoice (`document_id`, `amount`, optional `reason`) | **`202`** with an approval-request row (domain `payment`, action `credit_apply`); posts nothing yet |
| `POST customer-credits/{credit}/refund` | `payment.manage` | Request a cash refund (`cash_account_id`, optional `amount`, `reason`) | **`202`** with an approval-request row (action `credit_refund`); posts nothing yet |
| `POST customer-credits/{credit}/void` | `payment.manage` | Void an unapplied credit | — |

Apply/refund requests are decided through the shared approval-request endpoints with `domain=payment`: `GET approval-requests?domain=payment` (+ `/{id}`, `/{id}/history`) need `payment.view`; the maker can `PATCH` (edit the amount) / `submit` / `withdraw` a draft (`payment.submit`); `POST approval-requests/{id}/review` needs `payment.review`, `/approve` needs `payment.approve`, `/reject` needs `payment.reject`. Only one open request per credit. Maker, reviewer and approver must be three different users; approval posts the application/refund and the resulting journal entry carries the request's maker, reviewer and approver. Web: **Collections → Credit Approvals** (`/app/customer-credits/approvals`).

## Credit Notes & Debit Notes

Same CRUD + workflow shape as invoices, permission `sales.*`, at `credit-notes` (kind=`credit_note`) and `debit-notes` (kind=`debit_note`). Approving a credit note posts the reverse of an invoice's entries (Dr revenue / Cr AR) and reduces the linked invoice's outstanding balance when one is set; a debit note posts like an invoice (Dr AR / Cr revenue + output tax) representing an EOPT upward sales adjustment. Both support the same e-invoice lifecycle as invoices.

## Recurring Invoices (`recurring-invoices`)

Same shape as [recurring journal entries](https://bareledger.app/guides/recurring) — permission `sales.view`/`sales.manage`. A `source_document_id` must reference an **approved and posted** invoice (`422` otherwise).
