Returns, credit notes, and debit notes
A return is goods coming back. A sales return comes back from a customer and produces a credit note, which reduces what the customer owes you. A purchase return goes back to a supplier and produces a debit note, which reduces what you owe the supplier.
Both directions share one object, ProductReturn. Its type field tells you which direction a return is. A return also moves through one of two separate lifecycles, depending on how it was created. Choose the path first, because the two paths share no state machine.
Money, tax and currency rules are on money, pricing, and tax. The examples below use 0 for every amount. That zero stands for your own amount. The server calculates the real figures, so read them back from the response. Use "<ISO-8601 date>" for dates. For the exact field list of every type, see the API reference.
1. Choose the return path
Two flows create returns. Each has its own statuses and its own steps. Advance a return only with the operations of the flow that created it.
| Order-sourced flow | Invoice or RMA flow | |
|---|---|---|
| Create | createReturn, with a sales order or purchase order as the source and an optional grnId | createSalesReturn (source: a sales invoice), or createPurchaseReturn (source: a GRN or a purchase order) |
| Lands in | PENDING | APPROVED for a sales return; APPROVED or PENDING_APPROVAL for a purchase return |
| Advance with | processReturn, which moves it to PROCESSED, or to REJECTED with action: REJECT | Sales return: markReturnReceived, then processReturnInspection, which moves it to COMPLETED. Purchase return: posted at creation, or by approveRMA when it waits for approval |
| Who may call | Permission strings (see section 8) | The tenant role, not a permission string |
Return statuses are not guarded by a transition table.
The API writes these statuses: PENDING, PROCESSED, APPROVED, PENDING_APPROVAL, IN_TRANSIT, RECEIVED, INSPECTING, COMPLETED, REJECTED and PROCESSING. The status enum also declares DRAFT, CANCELLED and PARTIAL. Nothing writes those three, so a filter on them returns nothing.
Some cross-flow calls are refused and some are not. processReturn accepts only a PENDING return, so it refuses an APPROVED RMA return with Return has already been processed. processReturnInspection accepts only a RECEIVED return and refuses others with Return must be received before inspection. But markReturnReceived sets RECEIVED whatever the current status is, and approveRMA and rejectRMA on a sales return overwrite its status whatever it is. Never call the RMA operations on a return the order-sourced flow created, or on a return that is already finished.
2. Create a return from an order
Use createReturn when the goods are returned against a sales order or a purchase order.
Requires sales:credit_note:create. A PURCHASE_RETURN also requires purchase:debit_note:create.
mutation CreateReturn($input: CreateReturnInput!) {
createReturn(input: $input) {
id
returnNumber
status
type
currency
subtotal
taxAmount
totalAmount
items {
id
productId
variantId
quantity
unitPrice
totalAmount
condition
restockable
}
}
}
Variables:
{
"input": {
"type": "SALES_RETURN",
"originalOrderId": "<sales-order-id>",
"warehouseId": "<warehouse-id>",
"reason": "DAMAGED",
"reasonDescription": "<reason text>",
"items": [
{
"productId": "<product-id>",
"variantId": "<variant-id>",
"quantity": 2,
"unitPrice": 0,
"condition": "DAMAGED"
}
]
}
}
Rules for this step:
- Required input:
type,originalOrderId,reason,warehouseIdanditems. Each item needsproductId,variantId,quantity,unitPriceandcondition. Returns are tracked per variant, sovariantIdis always required. - This mutation accepts
SALES_RETURNandPURCHASE_RETURN. TheReturnTypeenum lists more values, but this mutation does not accept them. reasontakesDAMAGED,DEFECTIVE,EXPIRED,INCORRECT,NOT_REQUIREDorOTHER. ItemconditiontakesDAMAGED,DEFECTIVE,EXPIREDorGOOD.- A sales return needs an order that has shipped or been invoiced. Otherwise the call is refused with a message that starts
Sales order <order-number> is <STATUS>: a return can only be created once the order has been shipped, delivered or invoiced. - Each item is matched to the first line of the source order for that variant that still has room to return. The input type declares
originalOrderItemId, butcreateReturndoes not use it today, so you cannot pick a specific line when the order lists the same variant twice. - The returnable quantity is capped per source line. Earlier returns count against the cap unless they are
CANCELLEDorREJECTED. A sales return cannot exceed the ordered quantity. A purchase return cannot exceed the received quantity on its purchase order line. A refused call starts withCannot return <quantity> units for product <product>. Only <available> available. - The refund value comes from the original order line. It is net of the line discount and of the order discount for that line, and it carries tax at the source document's own rate and method. The
unitPriceyou send is required, but it is for your reference. Do not expect the server to use it, and do not compute the refund yourself. ReadtotalAmounton the return and on the note it produces. - Currency and exchange rate are inherited from the source document and are never re-rated.
- A purchase return needs the purchase returns feature on your plan. Otherwise the call is refused with a plan error.
- Visibility follows permissions in each direction. A caller who holds only sales permissions cannot list or read purchase returns, and the reverse is also true.
To read a return back, use returnById(id: String!) or returns(filters: ReturnFilterInput, skip: Int, take: Int). returnById requires sales:credit_note:read or purchase:debit_note:read, depending on the direction of the return. returns requires sales:credit_note:list or purchase:debit_note:list. returns is offset-paginated with skip and take, as described in errors, pagination, and idempotency.
The fields you will usually select on ProductReturn are id, returnNumber, status, type, reason, currency, subtotal, taxAmount, totalAmount, items, creditNotes and debitNotes.
3. Process a return
processReturn is the only step in the order-sourced flow that restocks goods and mints a note.
Requires sales:credit_note:create. A purchase return also requires purchase:debit_note:create.
mutation ProcessReturn($input: ProcessReturnInput!) {
processReturn(input: $input) {
message
creditNoteId
debitNoteId
updatedReturn {
id
returnNumber
status
processedAt
items {
id
acceptedQuantity
rejectedQuantity
restockable
}
creditNotes {
id
creditNoteNumber
status
totalAmount
}
debitNotes {
id
debitNoteNumber
status
totalAmount
}
}
}
}
Variables:
{
"input": {
"returnId": "<return-id>",
"action": "PROCESS",
"processedItems": [
{
"itemId": "<return-item-id>",
"acceptedQuantity": 1,
"rejectedQuantity": 1,
"restockable": false,
"disposalReason": "<disposal reason>"
}
],
"creditNoteDetails": {
"amount": 0,
"taxAmount": 0,
"notes": "<notes text>"
}
}
}
Rules for this step:
- Restocking happens only through ARC's stock movement path, in the same transaction as the status change. Do not post your own stock adjustment for a returned item.
- Direction is signed. On a sales return, accepted quantity comes back into stock. On a purchase return, accepted quantity leaves stock for the supplier.
- Quantities are in the document's unit of measure, but inventory moves in base units. Each movement is the accepted quantity multiplied by the source line's conversion factor. The factor is taken from the source line when the return is created, and you cannot send it. For example, a return of 1 case with a factor of 12 restocks 12 base units.
- Only items with
restockable: trueand anacceptedQuantityabove zero move stock. An item with no variant is skipped. - On a sales return,
restockable: trueis a request, not the decision. A line whose recordedconditionisDAMAGED,DEFECTIVEorEXPIREDis never put back into sellable stock, and the storedrestockablereadsfalse. On a purchase return the flag you send is used, because the goods leave stock for the supplier. - For each item, accepted plus rejected must not exceed the quantity returned on that line, and no count may be negative. A refused line moves no stock and creates no note. The message starts
Return <return-number> line <n> (<name>): accepted. - Every
processedItems[].itemIdmust belong to this return. Otherwise the call is refused withReturn item <item-id> does not belong to return <return-number>. All item checks run before any write, so one bad id fails the whole call. - Two concurrent calls cannot restock twice. Only one claims the
PENDINGreturn. The other is refused withReturn has already been processedorReturn changed or has already been processed. Refresh it before continuing. actionis required and acceptsAPPROVE,PROCESSorREJECT.PROCESSandAPPROVEboth process the return toPROCESSED.REJECTcloses the return asREJECTED: every line is recorded as fully rejected and not restockable, no stock moves and no note is minted, even if you pass note details. To reject only part of a return, sendPROCESSwithrejectedQuantityon the lines concerned.- A note is minted only when you pass
creditNoteDetails(sales) ordebitNoteDetails(purchase). The response then carriescreditNoteIdordebitNoteId. Each takesamountandtaxAmount, plus optionaladjustmentsandnotes. A credit note minted here startsPENDING. - The note's value is capped by the value of the accepted lines, including their tax and any shipping on the return. A larger note is refused with
Credit note total <amount> exceeds the value of the accepted return lines (<amount>). Adjust the amount, or ask an approver with the 'returns:return:override-note-amount' permission to issue it.For a purchase return the message saysDebit note total. Only callers holdingreturns:return:override-note-amountcan exceed the cap. The tenantADMINrole does not grant it. Overrides are audited. - A debit note minted here is always written at the value of the accepted lines. The
amountandtaxAmountyou send are not used for it, and a purchase return can have only one live debit note. - If you pass no note details, the return still restocks and no note is created. Create the note separately if you need one: a credit note as in section 5, or a debit note as in section 7.
- Customer-portal returns create no stock movement. Only staff processing touches inventory.
4. The invoice and RMA flow
Use this flow for goods returned against a sales invoice, and for goods sent back to a supplier when you want a receive-and-inspect cycle. Its operations check the tenant role, not permission strings.
The RMA operations require the tenant role itself, matched exactly against me.role. A user whose role is not listed for an operation is refused (OWNER is not listed for any of them), even when it holds the credit-note or debit-note permissions that let it run processReturn. Confirm the integration's user holds the role.
Operations and their requirements:
createSalesReturn(input: CreateSalesReturnInput!): ReturnResponse!requires the tenant roleADMINorMANAGER. Its input takescustomerId,invoiceId,items,reason(DAMAGED,INCORRECT,DEFECTIVE,EXPIRED,NOT_REQUIREDorOTHER),refundMethodandwarehouseId. Optional fields arecustomerRequest,requiresPickupandpickupAddress. Each item takesproductId,variantId,quantity(a whole number), andcondition(free text).refundMethodtakesBANK_TRANSFER,CASH,CREDIT_CARD,CREDIT_NOTE,DEBIT_NOTE,REPAIR,REPLACEMENTorSTORE_CREDIT.createPurchaseReturn(input: CreatePurchaseReturnInput!): ReturnResponse!requires the tenant roleADMIN,MANAGERorWAREHOUSE_STAFF, and the purchase returns feature on your plan. Its input takessupplierId,warehouseId,reason(the same six codes),items, and eithergrnIdorpurchaseOrderId. The optionalrequiresApprovalflag sends the return to approval first.approveRMA(returnId: String!, terms: [String!]!): ReturnResponse!requires the tenant roleADMINorMANAGER.rejectRMA(returnId: String!, reason: String!): ReturnResponse!requires the tenant roleADMINorMANAGER.markReturnReceived(returnId: String!, receivedItems: [PartialReturnItemInput!]): ReturnResponse!requires the tenant roleADMIN,MANAGERorWAREHOUSE_STAFF. It moves the return toRECEIVED.processReturnInspection(returnId: String!, input: InspectionInput!): ReturnActionResult!requires the tenant roleADMIN,MANAGERorWAREHOUSE_STAFF. It is the step that restocks and issues the credit note. The example below shows it.
Pickup and tracking:
scheduleReturnPickup(returnId: String!, input: SchedulePickupInput!): ReturnResponse!requires the tenant roleADMINorMANAGER. Withoutinput.deliveryit only records the pickup. Withinput.deliveryit books a courier, and then it also requiressales:shipment:create.bookReturnPickup(productReturnId: ID!, delivery: ShipmentBookingInput!): ShipmentGql!requiressales:shipment:createand the delivery partners feature on your plan. When the carrier reports the parcel as collected, the return moves fromAPPROVEDtoIN_TRANSIT. Receiving and the credit note stay manual.updateReturnTracking(returnId: String!, courierService: String!, trackingNumber: String!): ReturnResponse!requires the tenant roleADMINorMANAGER.
Two more RMA operations exist. createWarrantyReturn requires the tenant role ADMIN or MANAGER and is a separate warranty flow that this page does not cover. createPartialReturn requires the tenant role ADMIN or MANAGER and lands in PENDING_APPROVAL.
The RMA resolver also has list reads, such as getReturn, which requires the tenant role ADMIN, MANAGER or WAREHOUSE_STAFF. For new code, prefer returnById and returns from section 2, because they follow the permission scoping described there.
Rules for this flow:
createSalesReturnlands inAPPROVEDand needs inspection. It does not restock anything and does not issue a credit note, because the goods have not come back yet. The response givesid,returnNumber,rmaNumberandstatus.- A sales return must be raised within 30 days of the invoice date. A later attempt is refused with
Return window of 30 days has expired. SettingcustomerRequesttotruebypasses the window. The window is fixed and is not a tenant setting. - Item quantities on
createSalesReturn,createPurchaseReturnandprocessReturnInspectionare whole numbers. - A sales return line is restockable only when the
conditionyou send oncreateSalesReturnisNEW,LIKE_NEWorGOOD(any letter case). Any other condition is stored as not restockable, and inspection never restocks it. markReturnReceivedmoves the return toRECEIVED. ThereceivedItemsyou send are recorded for the audit trail only. Per-line counts are given at inspection.processReturnInspectionrequires the return to beRECEIVED. It claims the return atomically, so a second inspection that runs at the same time is refused withReturn changed or has already been processed. Refresh it before continuing.It restocks only therestockQuantity, and only for lines whose storedrestockableflag is true. The quantity moved is the restock quantity multiplied by the invoice line's conversion factor. For each line, accepted plus rejected must not exceed returned, and restock must not exceed accepted. AnitemIdthat is not a line of this return is ignored, not refused, so check your ids. The return finishes inCOMPLETED.- When
refundMethodisCREDIT_NOTE, the credit note is issued during inspection, in the same transaction as the restock. It covers the accepted quantity only, so nothing accepted means no note. When only part of the goods is accepted, the note's figures are scaled by accepted divided by returned.creditNoteIdin the result identifies the note. Its lines are then locked (linesLockedistrue) and cannot be changed. - A purchase return without
requiresApprovallands inAPPROVEDand posts its stock-out and supplier debit note when it is created. One withrequiresApprovalset lands inPENDING_APPROVAL. While it waits, it posts nothing: no stock movement and no debit note.approveRMAon that return posts the stock-out, at the quantity multiplied by the line's factor, and the debit note, exactly once. Approving an alreadyAPPROVEDpurchase return returns it unchanged, and any other status is refused.rejectRMAis refused on a purchase return once stock or a debit note has been posted, and on anyAPPROVEDpurchase return. A purchase return does not go through receive and inspect. - A purchase return whose unit cost cannot be resolved is refused with
Cannot resolve a unit cost for product <product> on this purchase return; supply a GRN with cost, a purchase order, or set the variant cost. - The debit note for a purchase return is capped at the quantity accepted on the goods receipt, and its tax follows the source line's rate.
- Scrap and repair quantities are recorded on each item, but nothing acts on them yet.
Example, inspecting a received return:
mutation InspectReturn($returnId: String!, $input: InspectionInput!) {
processReturnInspection(returnId: $returnId, input: $input) {
success
message
creditNoteId
}
}
Variables:
{
"returnId": "<return-id>",
"input": {
"overallResult": "PARTIAL",
"items": [
{
"itemId": "<return-item-id>",
"inspectionResult": "RESELLABLE",
"acceptedQuantity": 3,
"rejectedQuantity": 1,
"restockQuantity": 3,
"scrapQuantity": 0,
"repairQuantity": 0,
"qualityScore": 8
}
]
}
}
Every item needs itemId, inspectionResult, acceptedQuantity, rejectedQuantity, restockQuantity, scrapQuantity, repairQuantity and qualityScore. overallResult takes FAIL, PARTIAL or PASS. inspectionResult takes DONATE, RECYCLE, REPAIRABLE, RESELLABLE, RETURN_TO_VENDOR or SCRAP.
5. Credit notes
A credit note reduces what a customer owes you. Use one to amend an invoice, or to settle goods that came back and were processed through the order-sourced flow. Amending a confirmed order is covered at the end of this section.
| Operation | Requires | Purpose |
|---|---|---|
createCreditNote(input: CreateCreditNoteInput!) | sales:credit_note:create | Create a note |
creditNote(id: ID!) | sales:credit_note:read | Read one note |
creditNotes(filters: CreditNoteFiltersInput, skip: Int, take: Int) | sales:credit_note:list | List notes. creditNotesCount(filters:) returns the matching count |
updateCreditNote(id: ID!, input: UpdateCreditNoteInput!) | sales:credit_note:update | Edit a note while it is unapplied |
applyCreditNote and voidCreditNote | sales:credit_note:update | Apply to an invoice, or void. See section 6 |
removeCreditNote(id: ID!) | sales:credit_note:delete | Delete a draft or pending note |
creditNotes returns a plain list with skip and take. Use creditNotesCount for totals, as described in errors, pagination, and idempotency.
mutation CreateCreditNote($input: CreateCreditNoteInput!) {
createCreditNote(input: $input) {
id
creditNoteNumber
status
type
reason
currency
totalAmount
appliedAmount
remainingAmount
originalInvoiceId
linesLocked
items {
description
quantity
unitPrice
amount
taxAmount
totalAmount
}
}
}
Variables:
{
"input": {
"type": "<type-label>",
"reason": "<reason text>",
"partnerId": "<customer-id>",
"originalInvoiceId": "<invoice-id>",
"amount": 0,
"items": [
{
"description": "<line description>",
"quantity": 1,
"unitPrice": 0,
"amount": 0,
"taxRate": 0
}
]
}
}
Rules for this step:
- A new note is always
PENDING.statusis a plain string, not an enum. The values areDRAFT,PENDING,APPROVED,PARTIALLY_APPLIED,FULLY_APPLIEDandCANCELLED.applyCreditNotemoves a note toPARTIALLY_APPLIEDorFULLY_APPLIED, andvoidCreditNotemoves it toCANCELLED. Cancelling the invoice a note was applied to can give the note back (toPENDINGorPARTIALLY_APPLIED) or cancel it with the invoice. No mutation moves a note toAPPROVEDtoday. This is current behavior, and it may change. typeis a free-text label. Use the labels your tenant already uses. Nothing checks it against a fixed list.- Required inputs are
type,reason,partnerId,amountanditems. Optional inputs includeoriginalInvoiceId,returnId,currency,taxAmount,adjustments,expiryDate,notesandinternalNotes. - Each item needs
description,quantity,unitPriceandamount. Optional item fields includeproductId,variantId,taxRate,taxAmount,discountPercent,discountAmount,discountAmountExactandtaxBreakdown. - The server recalculates line and header totals from the item identities and rates. The
amountandtaxAmountyou send are accepted, but where they disagree with the server's figures, the server's figures are stored. ReadtotalAmountfrom the response instead of assuming your own. - A note raised against an invoice, or against a return that came from an invoice, takes that invoice's currency and exchange rate. Any other note is in your organization's base currency. The
currencyinput is not used to choose the currency. - Credit on an invoice is capped at the invoice total, not its unpaid amount. All live credit against an invoice must stay within that total. A note tied to a return counts against the return's invoice when the return came from an invoice. A note with no invoice, or tied to a return from the order-sourced flow, is not capped by this rule. The refusals are:
Credit note amount <amount> exceeds the amount left to credit on invoice <invoice-number>(the message then gives the invoice total and the amount already credited);Invoice <invoice-number> is still a draft. Edit the draft instead of raising a credit note against it.;Invoice <invoice-number> is <status>. A credit note cannot be raised against it.
- Credit notes are not gated by a plan feature.
Read the return's creditNotes before you create a note against it with returnId. Do not rely on the server to refuse a second credit note for the same return. The invoice credit cap is the limit you can rely on.
Editing and deleting:
updateCreditNoteworks only while the note isDRAFTorPENDINGand nothing has been applied to it. Otherwise the call is refused withCannot update a credit note with status <status>…void it and issue a new one instead.The message suggests voiding, but voiding needs anAPPROVEDorPARTIALLY_APPLIEDnote (see section 6). Current behavior, and it may change.- A note issued by a return's inspection has
linesLockedset totrue. Its lines cannot be changed. removeCreditNoteworks only forDRAFTorPENDINGnotes. Otherwise the call is refused withOnly draft or pending credit notes can be deleted. The delete is a soft delete.
Amending a confirmed order:
Staff can edit an order's lines, shipping and order-level discount while the order is a draft, awaiting payment, or confirmed. Those edits are refused once anything has shipped or been short-closed, once the order is partly or fully invoiced, once any payment is recorded, for an order paid online, and while a live invoice references the order. For an order in that state, amend it by cancelling and re-creating it, or by issuing a credit note. If goods physically came back, also create a return. Staff edit an order with updateSalesOrder(id, input), which requires sales:sales_order:update. For the order lifecycle, see sales lifecycle.
To correct an invoiced price, issue a credit note against the invoice with originalInvoiceId. To correct quantities for goods that came back, create a return.
6. Apply and void credit notes
applyCreditNote settles an outstanding sales invoice from a credit note.
Requires sales:credit_note:update.
mutation ApplyCreditNote($id: ID!, $invoiceId: ID!, $amount: Float!) {
applyCreditNote(id: $id, invoiceId: $invoiceId, amount: $amount) {
id
creditNoteNumber
status
totalAmount
appliedAmount
remainingAmount
}
}
After the call, re-read the invoice with the invoice query to get its outstandingAmount. See invoices and payments for invoice reconciliation.
Rules for this step:
- The note's status must be
PENDING,APPROVEDorPARTIALLY_APPLIED. Otherwise the call is refused withCredit note must be approved to apply. amountmust be above zero (Applied amount must be positive) and no more than the note'sremainingAmount(Applied amount cannot exceed remaining amount).- The target must be a sales invoice for the same customer as the note, and it must be issued. Refusals include
A credit note can only be applied to a sales invoice,Invoice and credit note must belong to the same partner, andCannot apply a credit note to a <status> invoice. - The note and the invoice must share a currency. A mismatch is refused with
Cannot apply a <ISO-4217 code> credit note to invoice <invoice-number> in <ISO-4217 code>. Cross-currency application is not supported. amountcannot exceed the invoice's outstanding amount (Applied amount <amount> exceeds invoice outstanding amount <amount>). An invoice that is already paid is refused withInvoice <invoice-number> is already fully paid.- One note can settle several invoices. Its status moves to
PARTIALLY_APPLIED, and then toFULLY_APPLIEDonce its remaining amount reaches zero within a rounding tolerance. Re-readremainingAmountand the invoice'soutstandingAmount. Do not subtract the amounts yourself. - Concurrent applications against the same note or invoice are serialized, so one balance cannot be spent twice.
- Applying a credit note also records a settlement in the payment ledger, in the same transaction: a
COLLECTIONpayment with methodCREDIT_NOTE, statusAPPROVEDand the note's number asreferenceNumber, plus a payment allocation to the invoice. Treat it as a non-cash settlement. It cannot be reversed, re-allocated or voided as a payment;voidCreditNoteis the only way to undo it. See invoices and payments.
The refusal Credit note must be approved to apply is misleading. PENDING notes are accepted. Because no mutation approves a note, do not wait for an approval step before applying one.
Voiding:
voidCreditNote(id: ID!, reason: String!) requires sales:credit_note:update. It accepts only APPROVED or PARTIALLY_APPLIED notes, and refuses others with Only approved or partially applied credit notes can be voided. Voiding reverses every application the note made against invoices and sets the note to CANCELLED.
A PENDING note cannot be voided, because no operation moves it to APPROVED. Edit it, apply it or delete it while it is unapplied. A FULLY_APPLIED note cannot be voided either. This is current behavior, and it may change.
7. Debit notes
A debit note records what a supplier owes you back. You raise it against a supplier and apply it to a purchase invoice.
Debit notes need the debit notes feature on your plan, for reads as well as writes. Credit notes do not.
Operations:
| Operation | Requires | Purpose |
|---|---|---|
createDebitNote(input: CreateDebitNoteInput!) | purchase:debit_note:create, and the debit notes feature | Create a note. Returns DebitNoteResponse |
applyDebitNote(id: ID!, invoiceId: ID!, amount: Float!) | purchase:debit_note:update, and the debit notes feature | Apply to a purchase invoice |
cancelDebitNote(id: ID!) | purchase:debit_note:update, and the debit notes feature | Cancel a note that has not been applied |
updateDebitNote(id: ID!, input: UpdateDebitNoteInput!) | purchase:debit_note:update, and the debit notes feature | Edit a pending note |
removeDebitNote(id: ID!) | purchase:debit_note:delete, and the debit notes feature | Delete a note that has not been applied |
debitNote(id: ID!) | purchase:debit_note:read, and the debit notes feature | Read one note |
debitNotes(filters: DebitNoteFiltersInput, partnerId: ID, skip: Int, take: Int) | purchase:debit_note:list, and the debit notes feature | List notes. debitNotesCount(filters:) returns the matching count and has the same gates |
mutation CreateDebitNote($input: CreateDebitNoteInput!) {
createDebitNote(input: $input) {
success
message
debitNote {
id
debitNoteNumber
status
type
partnerId
currency
amount
taxAmount
totalAmount
appliedAmount
remainingAmount
returnId
originalInvoiceId
issueDate
}
}
}
Variables:
{
"input": {
"type": "ADJUSTMENT",
"partnerId": "<supplier-id>",
"originalInvoiceId": "<purchase-invoice-id>",
"amount": 0,
"taxAmount": 0,
"issueDate": "<ISO-8601 date>",
"notes": "<notes text>"
}
}
Rules for this step:
typemust be one ofPURCHASE_RETURN,ADJUSTMENT,PENALTYorOTHER. Every debit note is raised on a supplier. A sales return's note is a credit note, not a debit note.typecannot be changed after creation.- Required inputs are
type,partnerId,amountandissueDate. Optional inputs includetaxAmountandadjustments(both default to0),dueDate,originalInvoiceId,returnId,notesandinternalNotes. - A standalone note keeps the header figures you send. It has no lines.
- A debit note raised with
returnIdtakes the return's figures, and the figures you send may be overridden. A purchase return can have only one live debit note. createDebitNotereturns aDebitNoteResponsewithdebitNote,messageandsuccess. A failure arrives as a GraphQL error whose message starts withFailed to create debit note:.- A new note is
PENDING. Its statuses arePENDING,APPROVED,PARTIALLY_APPLIED,APPLIEDandCANCELLED. Debit notes useAPPLIEDwhere credit notes useFULLY_APPLIED. updateDebitNoteworks only while the note isPENDING(Cannot update debit note that is not in PENDING status). For a note raised from a return, the amount fields can only be set to the return's figures.- The note must be
PENDING,APPROVEDorPARTIALLY_APPLIEDto be applied.amountmust be above zero (Amount must be positive.) and no more than the note's remaining balance (Amount <amount> exceeds the debit note's remaining balance <amount>.). The invoice must be a purchase invoice in your organization and not cancelled, otherwise the call is refused withInvoice not found or not eligible for debit note application. The invoice must belong to the same supplier (Invoice and debit note must belong to the same partner) and share the note's currency.amountcannot exceed the invoice's outstanding amount. - Applying a debit note creates a
PAYMENT-type payment with methodDEBIT_NOTEand statusPAID, plus a payment allocation, in the same transaction. The application therefore appears in your payment ledger as a non-cash settlement that cannot be reversed or voided as a payment. See invoices and payments. - An apply refusal arrives with its message prefixed
Failed to apply debit note:. cancelDebitNoteandremoveDebitNoteare refused once the note has any application:Cannot cancel a debit note that has already been appliedandCannot delete a debit note that has already been applied. Debit notes have no void or reversal operation.
Applying a debit note:
mutation ApplyDebitNote($id: ID!, $invoiceId: ID!, $amount: Float!) {
applyDebitNote(id: $id, invoiceId: $invoiceId, amount: $amount) {
success
message
debitNote {
id
status
appliedAmount
remainingAmount
}
}
}
8. Permissions and roles at a glance
| Operation group | Requires |
|---|---|
createReturn, processReturn | sales:credit_note:create. A purchase return also needs purchase:debit_note:create |
returns, returnsSummary, returnById, returnAuditLogs | list or read on sales:credit_note or purchase:debit_note, for each direction you need. A grant in one direction does not open the other |
processReturn above the note cap | returns:return:override-note-amount. The tenant ADMIN role does not grant it |
createSalesReturn, approveRMA, rejectRMA, createWarrantyReturn, createPartialReturn, scheduleReturnPickup, updateReturnTracking | The tenant role ADMIN or MANAGER. scheduleReturnPickup with input.delivery also needs sales:shipment:create |
createPurchaseReturn, markReturnReceived, processReturnInspection, getReturn | The tenant role ADMIN, MANAGER or WAREHOUSE_STAFF. createPurchaseReturn also needs the purchase returns feature |
bookReturnPickup | sales:shipment:create, and the delivery partners feature |
| Credit notes | sales:credit_note:create, read, list, update (apply, void, edit) and delete |
| Debit notes | purchase:debit_note:create, read, list, update (apply, cancel, edit) and delete. Every debit-note operation, reads included, also needs the debit notes feature |
Customer-initiated returns (customerCreateReturn) are a buyer-portal operation that runs under the customer's own sign-in. They feed the order-sourced flow and create no stock movement. This page does not document that contract.
9. The deprecated createInternalReturn
createInternalReturn is deprecated. Every call is refused with Internal returns are not supported: move stock between warehouses with a stock transfer (transferInventory from <from-warehouse-id> to <to-warehouse-id>).
Use transferInventory(input: TransferInventoryInput!): StockMovement! instead. It requires inventory:stock_transfer:create and the stock transfers feature on your plan. See purchasing and inventory for the transfer contract.
What not to do
- Do not mix the two flows. Advance a return created by
createReturnonly withprocessReturn. Advance a return created bycreateSalesReturnorcreatePurchaseReturnonly with the RMA operations. Not every cross-flow call is refused, so the server will not always stop you. - Do not post your own stock adjustment for returned goods. The return restocks through the stock movement path. A second adjustment double-counts. Restock is signed (positive for sales, negative for purchases) and equals accepted quantity multiplied by the source line's conversion factor.
- Do not compute the refund yourself, and do not rely on the
unitPriceyou send. The server derives the value from the original order, invoice or goods receipt line. ReadtotalAmounton the return and on the note. - Do not rely on
restockable: trueto restock a sales return line recorded asDAMAGED,DEFECTIVEorEXPIRED. The server refuses to put it back into sellable stock. On a purchase return the flag you send is used. - Do not send
action: REJECTto reject part of a return.REJECTrejects every line, moves no stock and mints no note. UsePROCESSwithrejectedQuantityfor a partial rejection. - Do not expect a credit note from
createSalesReturn. It is issued during inspection, for the accepted quantity only. Do not expectprocessReturnto mint a note withoutcreditNoteDetailsordebitNoteDetails. - Do not create a second credit note for a return that already has one. Read the return's
creditNotesfirst, because the server does not refuse a second credit note for the same return. - None of
createReturn,createSalesReturn,createPurchaseReturn,createCreditNote,createDebitNoteor any apply mutation takes anidempotencyKey. A retry after a timeout can create a duplicate. Before you retry, readreturns,creditNotesordebitNotesfiltered by partner and date, or read the return'screditNotesanddebitNotes. The caps on returnable quantity, invoice credit and note balance are the only backstops.transferInventoryis the exception: it does take anidempotencyKey. For the retry rules in general, see errors, pagination, and idempotency. - Do not apply a credit note across currencies, partners, invoice types or non-issued invoices. All of these are hard refusals. A credit note settles a sales invoice of the same customer. A debit note settles a purchase invoice of the same supplier.
- Do not treat note settlements in the payment ledger as cash. Applying a credit note or a debit note writes a payment with method
CREDIT_NOTEorDEBIT_NOTE. It cannot be reversed or voided as a payment. - Do not plan to void a
PENDINGorFULLY_APPLIEDcredit note. OnlyAPPROVEDorPARTIALLY_APPLIEDnotes can be voided, and no mutation moves a note toAPPROVED. Edit, apply or delete it while it is unapplied. - Do not edit a credit note's lines after an inspection has issued it (
linesLockedistrue). Do not edit or cancel an applied debit note. - Do not expect the 30-day window for RMA sales returns to be a tenant setting. It is fixed.
customerRequest: truebypasses it. - Do not call
createInternalReturn. It is deprecated and always refuses. UsetransferInventory. - Do not filter returns by
DRAFT,CANCELLEDorPARTIAL. Nothing writes those statuses. - Plan the
returns:return:override-note-amountpermission before you build an automatic credit flow. The tenantADMINrole does not grant it. - Purchase-side features are gated by plan. The purchase returns, debit notes and stock transfer features each return a plan error. A plan error is not a problem with your payload.
- The RMA operations check the tenant role. A user without the listed role is refused, even when it holds the credit-note permissions. Verify the role with your tenant admin.
- Use placeholders such as
<customer-id>,<supplier-id>,<invoice-id>,<warehouse-id>and<ISO-4217 code>in your own mapping. Zeros in the examples stand for your own amounts.