Skip to content

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 happenedUse
An issued invoice that isn't fully paid billed too muchA credit note, issued with an allocation
A paid invoice billed too muchA credit note, issued with a refund
The invoice is still a draftEdit the invoice. Credit notes need an issued invoice.
Merchandise comes back and you want receiving, restocking, and the refund handledA Return. Returns and credit notes share each invoice line's remaining value.
Money goes back, but the invoice itself was rightA 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.

Credit note statusStartFinal
  • 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 note
Final values do not change again
  • draft
    A working document. Edit it as often as you like. It has no number and reserves no credit, and there is no way to delete it.
  • issued
    Numbered and frozen. Its lines and totals never change again. Its credit can be allocated to the invoice or refunded.
  • voidFinal
    Retired. Its corrections stop counting against the invoice lines, and available_money is 0.

Five money fields track where an issued note's value sits:

  • total_money is the face value, the sum of its lines.
  • allocated_money is credit applied to the invoice, net of reversed allocations.
  • pending_refund_money is credit held by refunds that haven't resolved yet.
  • refunded_money is credit paid out by refunds that succeeded.
  • available_money is 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:

Where a $332.28 credit note's value sits
  • Allocatedallocated_money
    $200.00
  • Pending refundpending_refund_money
    $0.00
  • Refundedrefunded_money
    $50.00
  • Availableavailable_money
    $82.28
  • Totaltotal_money
    $332.28

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
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"}}
      }
    ]
  }'
Response
{
  "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:

reasonUse when
returned_goodsThe buyer returned goods they were billed for
order_adjustmentThe order changed after the invoice was issued
billing_errorThe invoice billed the wrong items, quantities, or amounts
goodwillYou're giving a courtesy credit
otherAnything 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
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, or external_reference_id keeps the stored line amounts.
  • Send "line_items": [] to empty the draft. null is rejected.
  • Send "memo": null or "external_reference_id": null to clear either one. reason can't be null.
  • A body with no editable field returns 400 INVALID_REQUEST with detail REQUIRED on body. 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.typeSendCredits
quantityquantity: positive whole units, no more than the line's remaining unitsThat share of the line's remaining billed value
amountamount_money: a positive amount in the invoice currency, no more than the line's remaining billed valueExactly 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:

NoteSubtotalDiscountTaxTotalAmount before tax
First4006756333277
Second4016756334278
Third3996655333278
Sum12002001671000833

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_quantity or details[0].maximum_money gives what's left.
  • An invoice_line_item_id that isn't on this invoice: 404 RESOURCE_NOT_FOUND.
  • amount_money in another currency: 400 INVALID_REQUEST with detail NOT_ALLOWED.
  • The same invoice_line_item_id twice, the wrong field for the type, 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:

BodyEffectInvoice status
NoneIssues 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 nothingopen or partially_paid
{"refund": {...}}Issues the note and requests a refund of its creditpaid

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 sets available_money to the total.
  • Copies seller_tax_identity and buyer_tax_identity from 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_REQUEST on line_items.
  • A note that is already issued returns 409 ACTION_NOT_ALLOWED with reason unsupported_state, or terminal_status for a void note. details[0].current_status names 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_ALLOWED with reason not_offered. If they fit but their amounts would change, it returns 409 VERSION_CONFLICT with no param and no reason, naming the credit note with resource_type, resource_id, and current_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_PROGRESS naming the note that holds the number. Retry with the same request and Idempotency-Key.

Refresh a stale draft#

  1. Retrieve the draft. GET returns the stored amounts.
  2. Send PATCH /v1/credit-notes/{credit_note_id} with its current line_items corrections, each keeping its credit_note_line_item_id, and its current version as expected_version. Flint recalculates them from what's left. If a correction no longer fits, reduce it.
  3. 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
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": {}}'
Response
{
  "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
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
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's outstanding_money minus any unpaid late fees. Credit doesn't pay late fees. Either breach returns 409 LIMIT_EXCEEDED, and details[0].maximum_money is 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, and details[0].maximum_money leaves 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, or void invoice has nothing outstanding, so any allocation returns 409 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 reason status_not_reached for a draft or terminal_status for 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 credited and invoice.credited fires.
  • With collected payments, it becomes paid, stamps paid_at, and invoice.paid fires.

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
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
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"
    }
  }'
Response
{
  "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
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
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#

TypeScript
// 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,
});

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
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
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:

FilterMatches
invoice_idNotes against one invoice
customer_idNotes whose source invoice currently belongs to the customer, across all their invoices. One valid customer ID.
statusdraft, issued, or void. Repeat the key or comma-separate values to match any of them.
reasonOne or more of the five reasons, the same way
external_reference_idYour identifier, exactly
queryPart of a memo, number, or external reference, or an ID from its start, such as cn_01
created_after, created_beforecreated_at from the first, inclusive, to the second, exclusive
cURL
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#

  1. What happens: You create or edit a draft
  2. Flint sends: credit_note.created
    A draft was created.
  3. Flint sends: credit_note.updated
    A draft's fields or lines changed. Replacing lines has no event of its own, so re-read the lines on every updated.
  4. What happens: You issue the note
  5. Flint sends: credit_note.issued
    The note has its number and its lines are frozen.
  6. What happens: You allocate credit, on issue or later
  7. Flint sends: credit_note_allocation.created
    Credit was applied to the invoice.
  8. Flint sends: invoice.creditedorinvoice.paid
    Only when the allocation closes the invoice: credited with no collected payments, paid with them.
  9. What happens: You reverse an allocation
  10. Flint sends: credit_note_allocation.reversed
    The allocation has reversed_at.
  11. Flint sends: invoice.reopened
    Only when a credited or paid invoice reopens.
  12. What happens: You request a refund, and it resolves
  13. Flint sends: credit_note.updated
    When the refund is requested and each time its outcome changes the note's refund amounts.
  14. Flint sends: refund.createdorrefund.succeededorrefund.partially_succeededorrefund.failed
    The linked refund's own lifecycle. See refund events.
  15. What happens: You void the note
  16. Flint sends: credit_note.voided
    The note is retired.

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.

  • HTTP 400
    param names the input. Common causes: a reason outside the five values; a memo over 4,096 characters; a correction with the wrong field for its type, a non-positive value, or more than the frozen line; an invoice_line_item_id used twice; amount_money in the wrong currency (detail NOT_ALLOWED); line_items without expected_version (detail REQUIRED); an edit with no editable field (REQUIRED on body); issuing a note with no lines; allocation with refund; a missing Idempotency-Key on issue, allocate, or refund; or an invalid list filter.
  • HTTP 404
    The note, allocation, or invoice doesn't exist, or param names an invoice_line_item_id that isn't on this invoice. Use an ID from the invoice's snapshot.line_items.
  • HTTP 409
    When creating: reason status_not_reached means the invoice is still a draft, and terminal_status means it's void. When editing or issuing: unsupported_state means the note is already issued, so create another note; terminal_status means it's void. When allocating, refunding, voiding, or downloading the PDF: status_not_reached means the note is a draft, and terminal_status means it's void.
  • HTTP 409
    When issuing, reason not_offered means the stored corrections no longer fit the invoice lines' remaining value. Refresh the draft. When refunding, not_offered means the invoice isn't paid; current_status names its status.
  • HTTP 409
    When reversing, reason already_applied means the allocation was already reversed.
  • HTTP 409
    Reason credit_note_tax_reconciliation_required means an earlier issued note on the same invoice line has tax amounts that don't reconcile, so Flint won't base a new correction, allocation, or refund on it. blocking_resources lists the notes. Existing reads and retries of committed requests keep their results. You can reverse that note's allocations and void it if no refund is pending or succeeded; reconcile any refunded money in your accounting records, then contact support before correcting the line again.
  • HTTP 409
    A correction is above the line's remaining pool, an allocation is above the note's available credit or the invoice's balance or would leave less than the unpaid tip, or a refund is above the available credit. details[0].maximum_money, or available_quantity for a quantity, gives the most you can send.
  • HTTP 409
    CONCURRENT_MODIFICATIONRetry the same request
    Reason credit_note_refund_retry_exhausted on a refund: retry the identical request with the same Idempotency-Key.
  • HTTP 409
    VERSION_CONFLICTRefresh, then retry
    expected_version is stale, including when another write lands while your request runs, or when issue would change the amounts the draft previewed. details[0].current_version is the note's version. Retrieve the note, refresh the draft if needed, and send the request again with a new key.
  • HTTP 409
    OPERATION_IN_PROGRESSRetry the same request
    When issuing, another note holds the next number: retry the same request with the same key. When allocating, an online payment on the invoice is resolving: retry once it settles.
  • HTTP 409
    When voiding a note: reverse the allocations in blocking_resources; a note with a pending or succeeded refund can't be voided. When voiding an invoice: void its issued credit notes first.
  • HTTP 409
    The key was used with a different request, such as an allocation with another amount. Send the original request, or use a new key for a new action.

Endpoints#

Was this helpful?