Credit notes
A credit note corrects an invoice you already issued. You pick the lines that were wrong, Flint computes what those lines are worth with their discount and tax share attached, and allocating the credit draws down the invoice balance. No money moves, and the customer gets a numbered document their accounts-payable team can file against the original.
That makes credit notes the right tool for goods that came back, a line billed at the wrong rate, and a goodwill adjustment on a balance the customer has not paid yet. They are the wrong tool for returning money the customer already sent. That is a refund against the backing order.
Credit reduces what is owed. A refund returns what was collected. If the invoice still has an outstanding balance, you want a credit note; if the card has already settled, you want a refund.
How a credit note works#
Every credit note belongs to one invoice, and that invoice has to be past draft and not void (INVOICE_NOT_CREDITABLE). Paid, uncollectible, and already-credited invoices all accept a credit note; what they reject later is the allocation, because there is no balance left to draw down.
Credit-note responses include version. Issue, void, allocation, and allocation reversal accept optional expected_version to reject changes based on an older note. Use the version returned by the latest successful write for the next edit.
The note itself moves through three states:
- draft moves to issued on issue
- draft on edit: lines can change freely
- issued moves to void on void
- issued on allocate: the invoice balance falls
Issuing is the line in the sand. Before it, the note is a working document you can edit as many times as you like. After it, the lines and totals are frozen, the note has a credit_note_number, the PDF exists, and the only remaining moves are allocating credit and reversing those allocations.
Two money fields track the note:
total_moneyis the face value, the sum of its lines.unallocated_moneyis what is left to apply. It starts equal tototal_moneyat issue, falls as you allocate, and rises again when you reverse an allocation.
Create a draft#
POST /v1/credit-notes takes the invoice, a reason, and an optional memo.
curl -X POST https://api.withflintpay.com/v1/credit-notes \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: credit-note-inv-1042-001" \
-d '{
"invoice_id": "inv_1kmn0aExample",
"reason": "returned_goods",
"memo": "Two of the six chairs came back damaged."
}'
{
"data": {
"credit_note_id": "cn_1kmn0aExample",
"invoice_id": "inv_1kmn0aExample",
"status": "draft",
"reason": "returned_goods",
"memo": "Two of the six chairs came back damaged.",
"credit_note_lines": [],
"total_money": {"amount": 0, "currency": "USD"},
"unallocated_money": {"amount": 0, "currency": "USD"}
}
}
reason is one of returned_goods, order_adjustment, billing_error, goodwill, or other (INVALID_CREDIT_NOTE_REASON). It prints on the credit note, so pick the one the customer would recognize. memo runs to 4096 characters (INVALID_CREDIT_NOTE_MEMO).
The note uses the invoice's currency. Include credit_note_lines on create to add corrections, or omit it for an empty draft. credit_note_number is absent on drafts.
PATCH /v1/credit-notes/{credit_note_id} edits reason, memo, and credit_note_lines. Omitted fields stay unchanged. Send "memo": null to clear it. Sending no mutable fields returns EMPTY_UPDATE, and unknown fields are rejected rather than ignored.
Credit specific lines#
PATCH /v1/credit-notes/{credit_note_id} replaces the complete credit_note_lines array when supplied. Send the note's last-read version as expected_version. Include credit_note_line_id to retain an existing line, omit the ID to create a line, and omit a previous line to remove it. Send [] to clear the draft. Null is invalid. A stale version returns CREDIT_NOTE_CHANGED; retrieve the note before retrying.
Each entry points at an invoice_line_item_id from the invoice's frozen snapshot and carries one correction:
curl -X PATCH https://api.withflintpay.com/v1/credit-notes/cn_1kmn0aExample \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"expected_version": 1,
"credit_note_lines": [
{
"invoice_line_item_id": "invli_1kmn0aExample",
"correction": {"type": "quantity", "quantity": 2}
},
{
"invoice_line_item_id": "invli_2bqr7dExample",
"correction": {"type": "amount", "amount_money": {"amount": 4000, "currency": "USD"}}
}
]
}'
{
"data": {
"credit_note_id": "cn_1kmn0aExample",
"status": "draft",
"credit_note_lines": [
{
"credit_note_line_id": "cnli_1kmn0aExample",
"invoice_line_item_id": "invli_1kmn0aExample",
"quantity": 2,
"description": "Oak side chair",
"subtotal_money": {"amount": 30000, "currency": "USD"},
"discount_money": {"amount": 3000, "currency": "USD"},
"tax_money": {"amount": 2228, "currency": "USD"},
"total_money": {"amount": 29228, "currency": "USD"}
}
],
"total_money": {"amount": 33228, "currency": "USD"},
"unallocated_money": {"amount": 0, "currency": "USD"}
}
}
Two correction types, and they are mutually exclusive:
quantitycredits whole units. Passquantityand nothing else. It cannot exceed the units on the source line.amountcredits a figure you name. Passamount_moneyand nothing else. It has to be positive, in the invoice currency, and no more than what that line is worth.
Sending the wrong pairing returns INVALID_CREDIT_NOTE_LINE; an unrecognized type returns INVALID_CREDIT_NOTE_CORRECTION_TYPE.
You do not compute the money. Flint derives subtotal_money, discount_money, and tax_money from the source line's own proportions, so crediting two of six chairs credits a third of that line's discount and a third of its tax. For an amount correction, the rounding residual lands in tax_money so total_money comes out to exactly the figure you asked for.
Each invoice_line_item_id can appear once per note (DUPLICATE_INVOICE_LINE). To credit the same line twice for different reasons, combine it into one line here or issue a second note.
Issue it#
curl -X POST https://api.withflintpay.com/v1/credit-notes/cn_1kmn0aExample/issue \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: issue-cn-1kmn0a-001"
Issue assigns credit_note_number, stamps issued_at, renders the PDF, and sets unallocated_money to the note's total. Numbering is sequential per merchant, starts at 1001, and carries the prefix from your invoice settings (CN- unless you changed it). Test mode and live mode number independently.
The note needs at least one line and a total above zero. Editing anything after issue returns CREDIT_NOTE_NOT_DRAFT.
The over-credit check runs at issue, not when you edit lines. Two drafts against the same invoice line can each look fine on their own and the second one fails at issue with CREDIT_NOTE_AMOUNT_EXCEEDS_CREDITABLE, because across every issued note a line can only be credited down to zero. If you keep more than one draft open against an invoice, expect to handle that error rather than treating issue as a formality.
Two merchants' notes never collide, but two of your own issues racing for the next number do: the second returns retryable CREDIT_NOTE_ISSUE_IN_PROGRESS and names the note holding the number. Retry with the same idempotency key.
Apply the credit#
Issuing a note does not change the invoice. Allocating it does.
curl -X POST https://api.withflintpay.com/v1/credit-notes/cn_1kmn0aExample/allocations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: allocate-cn-1kmn0a-001" \
-d '{"amount_money": {"amount": 33228, "currency": "USD"}}'
{
"data": {
"credit_note_allocation": {
"credit_note_allocation_id": "cna_1kmn0aExample",
"credit_note_id": "cn_1kmn0aExample",
"invoice_id": "inv_1kmn0aExample",
"amount_money": {"amount": 33228, "currency": "USD"},
"allocated_at": "2026-07-20T16:04:11Z",
"idempotency_key": "allocate-cn-1kmn0a-001"
},
"credit_note": {
"credit_note_id": "cn_1kmn0aExample",
"status": "issued",
"total_money": {"amount": 33228, "currency": "USD"},
"unallocated_money": {"amount": 0, "currency": "USD"}
},
"invoice": {
"invoice_id": "inv_1kmn0aExample",
"status": "open",
"outstanding_money": {"amount": 216772, "currency": "USD"}
}
}
}
Allocation is the one credit-note call that requires an Idempotency-Key (IDEMPOTENCY_KEY_REQUIRED). The key is the allocation's identity: it comes back on the allocation, it is queryable with GET /v1/credit-notes/{credit_note_id}/allocations?idempotency_key=..., replaying it with the same amount returns the original allocation, and reusing it with a different amount returns IDEMPOTENCY_KEY_REUSED. That is what makes a timed-out request safe to repeat.
Unlike every other credit-note endpoint, allocate and reverse return three objects: the allocation, the note with its new unallocated_money, and the recomputed invoice. You do not need a follow-up fetch to see the new balance.
The amount has to clear two ceilings, and both report as CREDIT_NOTE_ALLOCATION_EXCEEDS_BALANCE: the note's unallocated_money, and the invoice's outstanding balance. An invoice that is paid, void, uncollectible, or already credited has no outstanding balance, so any allocation against it fails. A note that is still draft returns CREDIT_NOTE_NOT_ISSUED.
Allocation is also rejected with retryable INVOICE_PAYMENT_RESOLVING while an online payment is resolving, for the same reason recording an offline payment is: the customer may be mid-payment for a balance you are about to change.
When allocations take the balance to zero and no money was collected, the invoice becomes credited, closed_at is stamped, and invoice.credited fires.
Reverse an allocation#
Applied the credit to the wrong invoice, or applied too much? Reverse it.
curl -X POST https://api.withflintpay.com/v1/credit-notes/cn_1kmn0aExample/allocations/cna_1kmn0aExample/reverse \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: reverse-cna-1kmn0a-001"
Reversal is append-only: the original allocation keeps its row and gains reversed_at, so the history reads as what happened rather than what it ended up looking like. The credit returns to unallocated_money and the invoice balance reopens. A credited invoice that reopens goes back to open or partially_paid and emits invoice.updated. Handle that event: credited is not a terminal status the way paid and void are.
Reversing with the same idempotency key twice returns the same reversal. Reversing an already-reversed allocation under a different key returns CREDIT_NOTE_ALLOCATION_ALREADY_REVERSED.
Void a credit note#
Void retires a note that should not have been issued.
curl -X POST https://api.withflintpay.com/v1/credit-notes/cn_1kmn0aExample/void \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: void-cn-1kmn0a-001"
Every allocation has to be reversed first (CREDIT_NOTE_HAS_ALLOCATIONS), and only issued notes void (CREDIT_NOTE_NOT_ISSUED). Void is terminal: there is no un-void, and there is no way to delete a draft. A draft you no longer want can simply be left, or emptied with PATCH /v1/credit-notes/{credit_note_id}, sending its current expected_version and credit_note_lines: [].
Issuing a credit note also locks the invoice against voiding: an invoice with an issued credit note against it returns INVOICE_HAS_ISSUED_CREDIT_NOTE. Void the credit note first, then the invoice.
Download the PDF#
curl https://api.withflintpay.com/v1/credit-notes/cn_1kmn0aExample/pdf \
-H "Authorization: Bearer YOUR_API_KEY" \
-o credit-note-CN-1001.pdf
The PDF carries your branding, the credited lines, and the original invoice number. It exists from issue onward; asking for a draft's PDF returns CREDIT_NOTE_NOT_ISSUED. The endpoint returns application/pdf directly, so pipe it to a file or proxy it to your own back office.
Show it to the customer#
Three read endpoints work from a customer session or from the invoice access token in a hosted invoice link, so a buyer looking at their invoice can see what was credited:
GET /v1/me/invoices/{invoice_id}/credit-notesGET /v1/me/invoices/{invoice_id}/credit-notes/{credit_note_id}GET /v1/me/invoices/{invoice_id}/credit-notes/{credit_note_id}/pdf
These return issued and void notes, newest first. Drafts are not visible to the buyer and return 404. The buyer shape drops unallocated_money and the merchant identifiers, because how much credit is left to apply is your bookkeeping, not theirs.
Query credit notes#
GET /v1/credit-notes returns your notes newest first, filterable by invoice_id and by status (draft, issued, or void; anything else returns INVALID_CREDIT_NOTE_STATUS).
curl -G https://api.withflintpay.com/v1/credit-notes \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "invoice_id=inv_1kmn0aExample" \
--data-urlencode "status=issued"
GET /v1/credit-notes/{credit_note_id}/allocations does the same for one note's allocations, including the reversed ones. All list endpoints paginate with page_token; see Pagination.
Handle webhook events#
curl -X POST https://api.withflintpay.com/v1/webhook-endpoints \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: webhook-credit-notes-001" \
-d '{
"url": "https://example.com/webhooks/flint",
"enabled_events": [
"credit_note.issued",
"credit_note.allocation_created",
"credit_note.allocation_reversed",
"credit_note.voided",
"invoice.credited"
],
"description": "Credit note lifecycle"
}'
Replacing lines arrives as credit_note.updated, not as its own event, so a handler that syncs credit notes into your ledger should re-read the lines on every updated.
Both credit_note.* families need commerce.credit_notes.read on the endpoint's key. See the Webhooks guide for registration, signature verification, and retry behavior.
Errors you will encounter#
All errors use the standard error envelope.
quantity or amount, within the source linequantity or amount, within the source lineinvoice_line_item_idCREDIT_NOTE_ISSUE_IN_PROGRESSRetryableIdempotency-Key; reuse it when retryingINVOICE_PAYMENT_RESOLVINGRetryablereversed_at to confirmRelated docs#
- Credit Notes API Reference: every field on every endpoint.
- Invoicing: the invoice a credit note corrects.
- Refunds: returning money that was already collected.
- Idempotency: safe retries, and why allocation insists on a key.
- Money & currency: minor units and currency handling.
