Credit notes
A credit note corrects an invoice you already issued: two chairs came back damaged, a line was billed at the wrong rate, or you're giving a courtesy credit. It's a numbered document that names the invoice lines it corrects and how much tax and discount each correction carries. The note then does one of two things with its value. On an unpaid invoice, you allocate the credit and the balance falls. On a paid invoice, you refund it to the buyer's original payment methods.
Issuing a note on its own moves no money and leaves the invoice balance unchanged. You choose what happens to the credit, in the same call or later.
Choose the right correction#
| What happened | Use |
|---|---|
| An issued invoice that isn't fully paid billed too much | A credit note, issued with an allocation |
| A paid invoice billed too much | A credit note, issued with a refund |
| The invoice is still a draft | Edit the invoice. Credit notes need an issued invoice. |
| Merchandise comes back and you want receiving, restocking, and the refund handled | A Return. Returns and credit notes share each invoice line's remaining value. |
| Money goes back, but the invoice itself was right | A refund on the order |
Credit notes need commerce.credit_notes.write. Refunding one also needs commerce.refunds.write.
How a credit note works#
Every credit note belongs to one invoice, and that invoice has to be issued and not void. Paid, uncollectible, and credited invoices accept a credit note too, but they have no balance left to allocate against, so on a paid invoice you refund the note instead.
- draft on edit: lines and fields can change freely
- draft moves to issued on issue
- issued on allocate or reverse: the invoice balance falls or reopens
- issued on refund: money returns on a paid invoice
- issued moves to void on void
status on credit noteFive money fields track where an issued note's value sits:
total_moneyis the face value, the sum of its lines.allocated_moneyis credit applied to the invoice, net of reversed allocations.pending_refund_moneyis credit held by refunds that haven't resolved yet.refunded_moneyis credit paid out by refunds that succeeded.available_moneyis what's left to allocate or refund. It's 0 on draft and void notes.
On an issued note they always balance: total_money = allocated_money + pending_refund_money + refunded_money + available_money. For example, a $332.28 note that cleared $200.00 of an invoice, then refunded $50.00 after the buyer paid the rest:
Every response includes the note's version. Edit, issue, allocate, reverse, refund, and void accept an optional expected_version, and a stale one returns 409 VERSION_CONFLICT with details[0].current_version. Replacing a draft's lines requires it. Use the version from the latest response, since allocations, refunds, and refund outcomes all move it.
memo and external_reference_id are always present and are null when unset. credit_note_number, issued_at, and voided_at appear once they apply.
Create a draft#
A credit note corrects specific lines of the invoice's frozen snapshot. Read the invoice with GET /v1/invoices/{invoice_id} and take each line's invoice_line_item_id from snapshot.line_items.
This draft credits two of six chairs on invoice 1042 and $40.00 of a scratched table:
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-1" \
-d '{
"invoice_id": "inv_01KF3Q8V2M6N4P7R9S1T3W5X7Y",
"reason": "returned_goods",
"memo": "Two chairs arrived damaged, and the table has a scratch.",
"line_items": [
{
"invoice_line_item_id": "invli_01KF3Q8V2M6N4P7R9S1T3W5X80",
"correction": {"type": "quantity", "quantity": 2}
},
{
"invoice_line_item_id": "invli_01KF3Q8V2M6N4P7R9S1T3W5X81",
"correction": {"type": "amount", "amount_money": {"amount": 4000, "currency": "USD"}}
}
]
}'
{
"data": {
"credit_note_id": "cn_01KF4B2C6D8E0F2G4H6J8K0M2N",
"merchant_id": "mer_01J00000000000000000000000",
"invoice_id": "inv_01KF3Q8V2M6N4P7R9S1T3W5X7Y",
"status": "draft",
"reason": "returned_goods",
"memo": "Two chairs arrived damaged, and the table has a scratch.",
"external_reference_id": null,
"line_items": [
{
"credit_note_line_item_id": "cnli_01KF4B2C6D8E0F2G4H6J8K0M2P",
"invoice_line_item_id": "invli_01KF3Q8V2M6N4P7R9S1T3W5X80",
"name": "Oak side chair",
"correction": {"type": "quantity", "quantity": 2},
"subtotal_money": {"amount": 30000, "currency": "USD"},
"discount_money": {"amount": 3000, "currency": "USD"},
"tax_money": {"amount": 2228, "currency": "USD"},
"total_money": {"amount": 29228, "currency": "USD"}
},
{
"credit_note_line_item_id": "cnli_01KF4B2C6D8E0F2G4H6J8K0M2Q",
"invoice_line_item_id": "invli_01KF3Q8V2M6N4P7R9S1T3W5X81",
"name": "Walnut dining table",
"correction": {"type": "amount", "amount_money": {"amount": 4000, "currency": "USD"}},
"subtotal_money": {"amount": 4106, "currency": "USD"},
"discount_money": {"amount": 411, "currency": "USD"},
"tax_money": {"amount": 305, "currency": "USD"},
"total_money": {"amount": 4000, "currency": "USD"}
}
],
"total_money": {"amount": 33228, "currency": "USD"},
"available_money": {"amount": 0, "currency": "USD"},
"allocated_money": {"amount": 0, "currency": "USD"},
"pending_refund_money": {"amount": 0, "currency": "USD"},
"refunded_money": {"amount": 0, "currency": "USD"},
"seller_tax_identity": null,
"buyer_tax_identity": null,
"version": 1,
"created_at": "2026-10-28T15:02:11Z",
"updated_at": "2026-10-28T15:02:11Z"
},
"request_id": "req_01KF4B2C6D8E0F2G4H6J8K0M3A"
}
The chair line was six chairs with a subtotal of $900.00, a $90.00 discount, and $66.83 of added tax. Crediting two of the six takes a third of each: $300.00 of subtotal, a $30.00 discount, and $22.28 of tax, for a $292.28 line.
The note uses the invoice's currency. line_items is optional on create; leave it out, or send [], for an empty draft. Idempotency-Key is optional on create and edit.
reason is required and prints on the document, so pick the one your customer would recognize:
reason | Use when |
|---|---|
returned_goods | The buyer returned goods they were billed for |
order_adjustment | The order changed after the invoice was issued |
billing_error | The invoice billed the wrong items, quantities, or amounts |
goodwill | You're giving a courtesy credit |
other | Anything else. Explain it in memo. |
Values match exactly, in lowercase. memo holds up to 4,096 characters and prints on the document. external_reference_id is your own identifier, 1 to 255 characters, and you can filter the list by it.
Edit the draft#
PATCH /v1/credit-notes/{credit_note_id} changes reason, memo, external_reference_id, and line_items on a draft. Fields you leave out stay as they are.
line_items replaces the whole set and requires expected_version. To keep a line, send it back with its credit_note_line_item_id, invoice_line_item_id, and correction, which each line returns in the same shape you send. Leave out the ID to add a line, and leave out a line to remove it. This edit raises the table credit to $60.00 and keeps the chairs:
curl -X PATCH https://api.withflintpay.com/v1/credit-notes/cn_01KF4B2C6D8E0F2G4H6J8K0M2N \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"expected_version": 1,
"line_items": [
{
"credit_note_line_item_id": "cnli_01KF4B2C6D8E0F2G4H6J8K0M2P",
"invoice_line_item_id": "invli_01KF3Q8V2M6N4P7R9S1T3W5X80",
"correction": {"type": "quantity", "quantity": 2}
},
{
"credit_note_line_item_id": "cnli_01KF4B2C6D8E0F2G4H6J8K0M2Q",
"invoice_line_item_id": "invli_01KF3Q8V2M6N4P7R9S1T3W5X81",
"correction": {"type": "amount", "amount_money": {"amount": 6000, "currency": "USD"}}
}
]
}'
The examples that follow keep the original $40.00 table credit, for a $332.28 note. Other edit rules:
- Replacing lines recalculates them from the invoice lines' current remaining value. Editing only
reason,memo, orexternal_reference_idkeeps the stored line amounts. - Send
"line_items": []to empty the draft.nullis rejected. - Send
"memo": nullor"external_reference_id": nullto clear either one.reasoncan't benull. - A body with no editable field returns
400 INVALID_REQUESTwith detailREQUIREDonbody. Unknown fields are rejected.
How Flint calculates each line#
Each line names one invoice_line_item_id, at most once per note, and one correction:
correction.type | Send | Credits |
|---|---|---|
quantity | quantity: positive whole units, no more than the line's remaining units | That share of the line's remaining billed value |
amount | amount_money: a positive amount in the invoice currency, no more than the line's remaining billed value | Exactly that amount, without using up units |
Flint works from the invoice line as it was frozen at issue, including modifiers and allocated merchandise discounts. Today's catalog prices, tax rates, and settings play no part. Credit notes correct merchandise lines; they don't credit charges, tips, or late fees.
A quantity correction takes its share of the line's remaining billed value, rounded half up once in the currency's minor units. An amount correction takes exactly amount_money. Flint then gives the line its proportional share of the remaining tax and discount, each rounded half up, and sets subtotal_money so the line balances. It never adjusts tax to hit a total. The line keeps the source line's tax mode:
- Added tax:
total_money = subtotal_money - discount_money + tax_money. - Included tax:
total_money = subtotal_money - discount_money. Tax is already inside the price. - Either way, the amount before tax is
total_money - tax_money.
For two tax-included units with a total of 1200 and tax of 200, crediting one unit, or an amount of 600, returns a total of 600 with tax of 100. Crediting both returns 1200 with tax of 200.
Remaining value is shared#
Each invoice line has a remaining pool of billed value, subtotal, discount, tax, and units. Issued, non-void credit notes and committed Return lines draw from the same pool, so together they can never exceed what the line billed. A Return request holds its value until that quantity is declined or canceled. Drafts hold nothing. Allocating, reversing, and refund outcomes don't change the pool; voiding an issued note gives its corrections back.
An amount correction lowers the remaining value without using units, so later quantity corrections divide what's left. Successive corrections can round differently, but they always use up the line exactly. With three units, a total of 1000, a subtotal of 1200, a discount of 200, and tax of 167 included, three one-unit notes credit:
| Note | Subtotal | Discount | Tax | Total | Amount before tax |
|---|---|---|---|---|---|
| First | 400 | 67 | 56 | 333 | 277 |
| Second | 401 | 67 | 56 | 334 | 278 |
| Third | 399 | 66 | 55 | 333 | 278 |
| Sum | 1200 | 200 | 167 | 1000 | 833 |
The same integer rules apply in every currency's own minor units, including JPY and BHD.
Line errors name the field in param, such as line_items[0].correction.quantity:
- More than the frozen line's units or billed total:
400 INVALID_REQUEST, with the limit in the message. - More than the line's remaining pool:
409 LIMIT_EXCEEDED.details[0].available_quantityordetails[0].maximum_moneygives what's left. - An
invoice_line_item_idthat isn't on this invoice:404 RESOURCE_NOT_FOUND. amount_moneyin another currency:400 INVALID_REQUESTwith detailNOT_ALLOWED.- The same
invoice_line_item_idtwice, the wrong field for thetype, a zero-value source line, or a quantity whose share rounds to zero:400 INVALID_REQUEST. For the last, credit more units or an amount.
Issue the credit note#
POST /v1/credit-notes/{credit_note_id}/issue numbers and freezes the draft. Its optional body decides what happens to the credit:
| Body | Effect | Invoice status |
|---|---|---|
| None | Issues the note. The invoice balance is unchanged. | Any issued, non-void invoice |
{"allocation": {}} | Issues the note and applies its credit to the balance, all or nothing | open or partially_paid |
{"refund": {...}} | Issues the note and requests a refund of its credit | paid |
allocation and refund can't be combined (400 INVALID_REQUEST, param: allocation). Issue requires an Idempotency-Key. Reusing the key with the same body returns the original result; with a different body it returns 409 IDEMPOTENCY_KEY_REUSED.
Issuing:
- Assigns
credit_note_number, using your invoice settings' credit note prefix (CN-unless you changed it) and a sequence that starts at 1001. Your live account and each sandbox number independently. - Stamps
issued_at, freezes the lines and totals, and setsavailable_moneyto the total. - Copies
seller_tax_identityandbuyer_tax_identityfrom the invoice. Later profile edits don't change the document. - Renders the PDF.
The response holds the note at data.credit_note, the invoice after issue at data.invoice, and data.credit_note_allocation or data.refund when you asked for one. Read the note's next version from data.credit_note.
Issue fails when the draft can't become a document:
- A note with no lines returns
400 INVALID_REQUESTonline_items. - A note that is already issued returns
409 ACTION_NOT_ALLOWEDwith reasonunsupported_state, orterminal_statusfor a void note.details[0].current_statusnames the status. - Drafts hold no credit, so another note can issue first and use up the same lines. If the stored corrections no longer fit, issue returns
409 ACTION_NOT_ALLOWEDwith reasonnot_offered. If they fit but their amounts would change, it returns409 VERSION_CONFLICTwith noparamand noreason, naming the credit note withresource_type,resource_id, andcurrent_version. Flint never changes previewed amounts during issue. For either, refresh the draft. - Two of your notes issuing at the same moment compete for the next number. The second returns
409 OPERATION_IN_PROGRESSnaming the note that holds the number. Retry with the same request andIdempotency-Key.
Refresh a stale draft#
- Retrieve the draft.
GETreturns the stored amounts. - Send
PATCH /v1/credit-notes/{credit_note_id}with its currentline_itemscorrections, each keeping itscredit_note_line_item_id, and its currentversionasexpected_version. Flint recalculates them from what's left. If a correction no longer fits, reduce it. - Review the new amounts, then issue with a new
Idempotency-Key.
Apply credit to an unpaid invoice#
Allocating credit lowers the invoice's outstanding_money and raises its credit_money. Do it when you issue, or later from an issued note.
Allocate when you issue#
Add allocation to the issue request. The note and the allocation succeed or fail together: if the allocation is refused, the note stays a draft with no number, and the invoice is unchanged. The objects in this response are abridged:
curl -X POST https://api.withflintpay.com/v1/credit-notes/cn_01KF4B2C6D8E0F2G4H6J8K0M2N/issue \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: issue-cn-inv-1042-1" \
-d '{"expected_version": 1, "allocation": {}}'
{
"data": {
"credit_note": {
"credit_note_id": "cn_01KF4B2C6D8E0F2G4H6J8K0M2N",
"invoice_id": "inv_01KF3Q8V2M6N4P7R9S1T3W5X7Y",
"status": "issued",
"credit_note_number": "CN-1001",
"issued_at": "2026-10-28T15:20:40Z",
"total_money": {"amount": 33228, "currency": "USD"},
"allocated_money": {"amount": 33228, "currency": "USD"},
"pending_refund_money": {"amount": 0, "currency": "USD"},
"refunded_money": {"amount": 0, "currency": "USD"},
"available_money": {"amount": 0, "currency": "USD"},
"version": 2
},
"invoice": {
"invoice_id": "inv_01KF3Q8V2M6N4P7R9S1T3W5X7Y",
"invoice_number": "1042",
"status": "open",
"total_money": {"amount": 204593, "currency": "USD"},
"paid_money": {"amount": 0, "currency": "USD"},
"credit_money": {"amount": 33228, "currency": "USD"},
"outstanding_money": {"amount": 171365, "currency": "USD"}
},
"credit_note_allocation": {
"credit_note_allocation_id": "cna_01KF4C9T5V7W9X1Y3Z5A7B9C1D",
"credit_note_id": "cn_01KF4B2C6D8E0F2G4H6J8K0M2N",
"invoice_id": "inv_01KF3Q8V2M6N4P7R9S1T3W5X7Y",
"amount_money": {"amount": 33228, "currency": "USD"},
"allocated_at": "2026-10-28T15:20:40Z",
"idempotency_key": "issue:issue-cn-inv-1042-1"
}
},
"request_id": "req_01KF4C9T5V7W9X1Y3Z5A7B9C2E"
}
Without allocation.amount_money, Flint applies the smaller of the note's total and what the invoice can take. Here all $332.28 fits. When the note is larger, the default fills the balance and leaves the rest in available_money. An explicit amount_money must be positive, in the invoice currency, and fit both.
The allocation's idempotency_key is issue: followed by the issue request's key. If you lose the response, send the identical issue request with the same key, or look the allocation up:
curl -G https://api.withflintpay.com/v1/credit-notes/cn_01KF4B2C6D8E0F2G4H6J8K0M2N/allocations \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "idempotency_key=issue:issue-cn-inv-1042-1"
From then on it's an ordinary allocation: it closes the invoice under the rules below, and you reverse it the same way.
Allocate from an issued note#
POST /v1/credit-notes/{credit_note_id}/allocations applies credit from a note that's already issued. amount_money and an Idempotency-Key are required. The key is the allocation's identity: it's stored on the allocation, filterable on the allocations list, and a retry with the same key and amount returns the original allocation instead of applying the credit twice.
curl -X POST https://api.withflintpay.com/v1/credit-notes/cn_01KF4B2C6D8E0F2G4H6J8K0M2N/allocations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: allocate-cn-1001-1" \
-d '{"amount_money": {"amount": 20000, "currency": "USD"}}'
The response returns credit_note_allocation, the credit_note with its new available_money and allocated_money, and the recomputed invoice, so you don't need a follow-up read.
Limits on an allocation#
- The amount can't exceed the note's
available_money, or the invoice'soutstanding_moneyminus any unpaid late fees. Credit doesn't pay late fees. Either breach returns409 LIMIT_EXCEEDED, anddetails[0].maximum_moneyis the most you can send. - The allocation must leave the order's unpaid tip outstanding, because credit lowers the balance but not the tip. Neither form shrinks to make room: an amount that cuts into the tip returns
409 LIMIT_EXCEEDED, anddetails[0].maximum_moneyleaves the tip collectible. Allocate that much, collect the rest, then refund what's left on the note once the invoice is paid. - A
paid,credited,uncollectible, orvoidinvoice has nothing outstanding, so any allocation returns409 LIMIT_EXCEEDED. On a paid invoice, refund instead. - While an online payment on the invoice is resolving, allocation returns
409 OPERATION_IN_PROGRESS. The buyer may be paying the balance you're about to change. Retry once the payment settles. - A note that isn't issued returns
409 ACTION_NOT_ALLOWED, with reasonstatus_not_reachedfor a draft orterminal_statusfor a void note.
On issue, these errors report param as allocation.amount_money.
What the allocation does to the invoice#
An allocation that leaves a balance changes outstanding_money and credit_money and nothing else. One that takes the balance to zero closes the invoice and stamps closed_at:
- With no collected payments, the invoice becomes
creditedandinvoice.creditedfires. - With collected payments, it becomes
paid, stampspaid_at, andinvoice.paidfires.
For example, a $1,000.00 invoice with $600.00 paid becomes paid when a $400.00 allocation takes outstanding_money to 0.
Reverse an allocation#
Applied credit to the wrong invoice, or too much? Reverse the allocation. Idempotency-Key and expected_version are optional.
curl -X POST https://api.withflintpay.com/v1/credit-notes/cn_01KF4B2C6D8E0F2G4H6J8K0M2N/allocations/cna_01KF4C9T5V7W9X1Y3Z5A7B9C1D/reverse \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: reverse-cna-1001-1"
The allocation keeps its record and gains reversed_at, so the history shows what happened. The amount returns to the note's available_money, allocated_money falls by the same amount, and the invoice balance reopens. The response has the same three objects as an allocation.
A credited invoice returns to open, and a paid invoice closed partly by credit returns to partially_paid. Either change clears closed_at, and paid_at on a paid invoice, and sends invoice.reopened. An invoice that was already open or partially_paid keeps its status. Neither credited nor paid is final, so handle invoice.reopened.
Reversing an already reversed allocation with a new key returns 409 ACTION_NOT_ALLOWED with reason already_applied. Read the allocation's reversed_at to confirm.
Refund a paid invoice#
When the invoice is paid, return the credit to the buyer with a linked refund. The money goes back to the payments collected through Flint on that invoice. Manually recorded payments can't be refunded this way. The refund carries credit_note_id, invoice_id, and the request's idempotency_key, and it fires the usual refund events.
Both refund calls need commerce.credit_notes.write and commerce.refunds.write, and both require an Idempotency-Key. refund.reason is required and takes the refund reasons; reason_message is optional.
Issue and refund in one call#
Invoice 1043 was paid in full and billed a shelf at the wrong rate. Its draft credits $40.00. This issues the draft and refunds all of it. The invoice object is abridged:
curl -X POST https://api.withflintpay.com/v1/credit-notes/cn_01KF5D3E5F7G9H1J3K5M7N9P1Q/issue \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: issue-cn-inv-1043-1" \
-d '{
"refund": {
"reason": "other",
"reason_message": "Shelf billed at the wrong rate"
}
}'
{
"data": {
"credit_note": {
"credit_note_id": "cn_01KF5D3E5F7G9H1J3K5M7N9P1Q",
"invoice_id": "inv_01KF2A4B6C8D0E2F4G6H8J0K2M",
"status": "issued",
"credit_note_number": "CN-1002",
"reason": "billing_error",
"total_money": {"amount": 4000, "currency": "USD"},
"allocated_money": {"amount": 0, "currency": "USD"},
"pending_refund_money": {"amount": 4000, "currency": "USD"},
"refunded_money": {"amount": 0, "currency": "USD"},
"available_money": {"amount": 0, "currency": "USD"},
"latest_refund_status": "pending",
"version": 2
},
"invoice": {
"invoice_id": "inv_01KF2A4B6C8D0E2F4G6H8J0K2M",
"invoice_number": "1043",
"status": "paid",
"outstanding_money": {"amount": 0, "currency": "USD"}
},
"refund": {
"refund_id": "ref_01KF5D4F6G8H0J2K4M6N8P0Q2R",
"status": "pending",
"amount_money": {"amount": 4000, "currency": "USD"},
"refunded_tip_money": {"amount": 0, "currency": "USD"},
"reason": "other",
"reason_message": "Shelf billed at the wrong rate",
"order_id": "ord_01KF2A3Z5Y7X9W1V3T5S7R9Q1P",
"credit_note_id": "cn_01KF5D3E5F7G9H1J3K5M7N9P1Q",
"invoice_id": "inv_01KF2A4B6C8D0E2F4G6H8J0K2M",
"idempotency_key": "issue-cn-inv-1043-1",
"version": 1
}
},
"request_id": "req_01KF5D4F6G8H0J2K4M6N8P0Q3S"
}
A pending refund is accepted, not finished. Leave out refund.amount_money to refund all available credit, or send a smaller amount. If the refund is refused, the note stays a draft.
Refund a note that's already issued#
POST /v1/credit-notes/{credit_note_id}/refunds takes the same fields at the top level and returns the Refund at data:
curl -X POST https://api.withflintpay.com/v1/credit-notes/cn_01KF5D3E5F7G9H1J3K5M7N9P1Q/refunds \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: refund-cn-1002-1" \
-d '{
"reason": "other",
"reason_message": "Remaining credit for the shelf correction"
}'
Flint resolves an omitted amount_money once, to the note's available_money when it accepts the request. An amount above available_money returns 409 LIMIT_EXCEEDED with details[0].maximum_money. If the invoice's eligible payments can't cover the amount, the request fails and no smaller refund is created. A refund on an invoice that isn't paid returns 409 ACTION_NOT_ALLOWED with reason not_offered and the invoice's current_status; allocate on an unpaid invoice instead.
Follow the refund#
While a refund is unresolved, its amount sits in pending_refund_money. When it succeeds, the amount moves to refunded_money. When it fails or is canceled, the amount returns to available_money, and you can request it again with a new key. A partially succeeded refund keeps the successful part. latest_refund_status on the note shows the most recent linked refund's status, and each change sends credit_note.updated and moves the note's version.
The invoice stays paid with no outstanding balance. Its refunded_money and refund_status track the refund, as for any invoice refund. The credit note stays issued.
To recover a refund after losing the response, list the note's refunds by the key you sent:
curl -G https://api.withflintpay.com/v1/credit-notes/cn_01KF5D3E5F7G9H1J3K5M7N9P1Q/refunds \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "idempotency_key=issue-cn-inv-1043-1"
Listing needs commerce.credit_notes.read and commerce.refunds.read. Results are newest first, include failed attempts, and paginate with page_size and page_token. Save the key with your business action, and retry an uncertain request with the identical body and key instead of starting a new one.
When the note changes while a refund request runs, Flint retries on its own, up to three attempts in total. If your expected_version went stale, you get 409 VERSION_CONFLICT with the new current_version: retrieve the note and decide again. If the attempts run out while your version still matches, you get 409 CONCURRENT_MODIFICATION with reason credit_note_refund_retry_exhausted: retry the identical request with the same Idempotency-Key.
A credit note refund creates a new refund. It doesn't attach an existing refund, record money you returned outside Flint, or create a store credit balance.
With the SDK or CLI#
// Issue a draft and refund all of its credit.
const result = await flint.creditNotes.issue(
draftNoteId,
{ refund: { reason: "other", reason_message: "Shelf billed at the wrong rate" } },
{ idempotencyKey: savedIssueKey },
);
const issuedNote = result.credit_note;
const refund = result.refund;
// Refund part of a note that's already issued.
const partial = await flint.creditNotes.createRefund(
issuedNoteId,
{ reason: "other", amount_money: { amount: "1000", currency: "USD" } },
{ idempotencyKey: savedRefundKey },
);
// Recover a refund after a lost response.
const recovered = await flint.creditNotes.listRefunds(issuedNoteId, {
idempotency_key: savedRefundKey,
});
flint credit-notes issue cn_01KF5D3E5F7G9H1J3K5M7N9P1Q --input issue-refund.json --idempotency-key "$ISSUE_KEY"
flint credit-notes refunds create cn_01KF5D3E5F7G9H1J3K5M7N9P1Q --input refund.json --idempotency-key "$REFUND_KEY"
flint credit-notes refunds list cn_01KF5D3E5F7G9H1J3K5M7N9P1Q --filter-idempotency-key "$REFUND_KEY"
--input takes the same JSON bodies as the API. Add --dry-run=client --output json to a write to inspect the request without sending it.
These calls require an idempotency key. Create and save it before the call, so a retry or a restarted process can reuse it.
Void a credit note#
Void retires an issued note that shouldn't have been issued. Its corrections stop counting against the invoice lines, so another note or Return can use that value.
curl -X POST https://api.withflintpay.com/v1/credit-notes/cn_01KF4B2C6D8E0F2G4H6J8K0M2N/void \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: void-cn-1001-1"
Reverse every active allocation first. A note with a pending or succeeded refund can't be voided at all. Either blocker returns 409 RESOURCE_IN_USE. details[0].blocking_resources lists the active credit_note_allocation and refund IDs, and details[0].blocking_resource_count gives the total.
Only issued notes void. A draft returns 409 ACTION_NOT_ALLOWED with reason status_not_reached, and a void note returns terminal_status. Void is final. Drafts can't be voided or deleted; leave an unwanted draft as it is, or empty it with PATCH and "line_items": [].
An issued credit note also blocks voiding its invoice: POST /v1/invoices/{invoice_id}/void returns 409 RESOURCE_IN_USE listing the notes in blocking_resources. Void those notes first.
Download the PDF#
curl https://api.withflintpay.com/v1/credit-notes/cn_01KF4B2C6D8E0F2G4H6J8K0M2N/pdf \
-H "Authorization: Bearer YOUR_API_KEY" \
-o CN-1001.pdf
The endpoint returns application/pdf, not JSON, so save it to a file or stream it to your back office. The document carries your branding, the credited lines, the tax identities, and the number of the invoice it corrects. Added tax prints as Tax. Included tax prints as an informational Includes tax row and isn't added again, and a note with both shows both. A draft has no PDF and returns 409 ACTION_NOT_ALLOWED with reason status_not_reached.
What the buyer receives#
When you issue a credit note, Flint emails it to the source invoice's recipient and CC addresses if the invoice has delivery_mode: "email" and a recipient email address. Invoices with delivery_mode: "caller_managed" don't send a credit note email.
The email attaches the credit note PDF and links to the invoice. It shows the credit amount, any amount applied to the invoice, any refund requested when issuing the note, the remaining credit, and the new balance when credit was applied. If the credit hasn't been applied or refunded, the email tells the buyer to contact you before paying an outstanding balance or to ask how they'll receive the credit. These amounts describe the note when it was issued; later allocations and refunds don't change that email.
Credit note emails follow customer_email_delivery.invoices. With merchant_sends, Flint suppresses them even if they were already queued. Send your own email from credit_note.issued and download the PDF with GET /v1/credit-notes/{credit_note_id}/pdf. See Customer email delivery.
Show credit notes to the buyer#
A buyer looking at an invoice can see what was credited. These routes take a customer session or the invoice's access token, as a custom invoice page uses:
Drafts aren't visible to the buyer and return 404. The buyer view keeps the number, reason, memo, lines with their correction, total, tax identities, and timestamps. It leaves out what's left to allocate (available_money and allocated_money), along with merchant_id, external_reference_id, and version. It keeps pending_refund_money, refunded_money, and latest_refund_status, so the buyer can see a refund on its way and when it arrived.
Find credit notes#
GET /v1/credit-notes returns your notes newest first. Filters combine with AND:
| Filter | Matches |
|---|---|
invoice_id | Notes against one invoice |
customer_id | Notes whose source invoice currently belongs to the customer, across all their invoices. One valid customer ID. |
status | draft, issued, or void. Repeat the key or comma-separate values to match any of them. |
reason | One or more of the five reasons, the same way |
external_reference_id | Your identifier, exactly |
query | Part of a memo, number, or external reference, or an ID from its start, such as cn_01 |
created_after, created_before | created_at from the first, inclusive, to the second, exclusive |
curl -G https://api.withflintpay.com/v1/credit-notes \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "customer_id=cus_01KF3Q7A1B3C5D7E9F1G3H5J7K" \
--data-urlencode "reason=returned_goods,billing_error" \
--data-urlencode "status=issued"
An invalid filter value returns 400 INVALID_REQUEST. A valid filter with no matches returns an empty page. GET /v1/credit-notes/{credit_note_id}/allocations lists one note's allocations newest first, including reversed ones, and filters by idempotency_key. Every list paginates with page_size and page_token; see Pagination.
Add expand=invoice to the list or to GET /v1/credit-notes/{credit_note_id} to include each note's source invoice summary next to invoice_id: its number, status, customer, due date, and total_money, paid_money, outstanding_money, and refunded_money. Expansion needs commerce.invoices.read as well as credit note read access; without it the request returns 403 INSUFFICIENT_SCOPE. Adding or dropping expand between pages keeps the page token valid. The buyer routes don't take these filters or expand.
Tax reporting#
Each credit note line records the tax it credits in tax_money. Sum it across the note's line_items for the tax the whole note credits.
Handle webhook events#
credit_note.* events carry the credit note in data.object, as GET /v1/credit-notes/{credit_note_id} returns it. credit_note_allocation.* events carry the allocation. Neither includes the invoice or refund from the issue response. Delivery is at least once and in no guaranteed order, so fetch the resource and act on its current state.
An endpoint needs commerce.credit_notes.read for both credit note families, commerce.invoices.read for invoice.*, and commerce.refunds.read for refund.*. See Webhooks for registration, signature verification, and retries.
Errors to handle#
Every error uses the standard error envelope. For refused actions, details[0].reason says why and details[0].current_status names the blocking status.
Endpoints#
Related docs#
- Credit Notes API reference: every field on every endpoint.
- Invoicing: the invoice a credit note corrects, and its balance fields.
- Refunds: refund statuses, reasons, and events.
- Returns: merchandise that comes back, sharing the same invoice line value.
- Idempotency: safe retries and recovery reads.
