Credits
Lower what a payer owes with a Wingspan V3 credit. Credit an open or paid invoice, carry a balance to later invoices, set an end date, and trace every application.
A credit lowers the amount Wingspan collects from a payer. You create it against one invoice or as a balance for the payer's later invoices. Wingspan applies it when the invoice is paid and adds an entry to the credit's ledger each time. The invoice keeps its line items and amount, so both the original charge and the credit stay on record.
You can credit any payer you invoice, including one that has never signed in to Wingspan. A payer whose Payer record is linked to a Wingspan Account reads the same record, with the same id and number, as a deduction at /v3/payments/deductions. Its payable lists the credit in prePaymentDeductionIds, with deductedAmount and pendingDeductionAmount. Linking starts the payer's reads and events from that point, and earlier events aren't replayed. Only the Account that created a credit can change it.
Credits need the payments.credit:read and payments.credit:write scopes.
Pick the right tool
| You want to | Use |
|---|---|
| Change the invoice itself so the payer sees corrected line items | Update the invoice while it's Created or Opened |
| Keep the invoice as issued and lower what the payer owes with a numbered credit note | A credit with invoiceId |
| Give money back on a paid invoice | A credit with invoiceId and disposition: Refund, or refund the invoice |
| Keep the amount for the payer's later invoices | A credit with no invoiceId, or with disposition: Balance |
A credit changes what a payer owes you. It never changes a payable or a payout. To recover money from a payee, use a deduction.
Credit an open invoice
A $1,200 invoice is open, and you agree to take $300 off. Create a credit that targets the invoice:
curl -X POST "https://api.wingspan.app/v3/payments/credits" \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"payerId": "Pz4Rk8Wq2Tn6Lx9Vb3Mc1H",
"invoiceId": "Iv7Hn2Qx5Rt8Kw3Lp6Zb9D",
"amount": 300.00,
"currency": "USD",
"reason": "ServiceFailure",
"memo": "Job ended two hours early",
"externalId": "adj-88213",
"metadata": { "jobId": "job_55102", "approvedBy": "ops_0042" },
"lineItems": [
{
"documentLineItemId": "Li3Wx8Kq1Tz6Nv4Rb7Hm2P",
"description": "Two hours not worked",
"amount": 300.00
}
]
}'// (trimmed)
{
"id": "Cr9Kd2Wm6Qx4Tn8Lv1Bz5R",
"number": "CR-000123",
"payerId": "Pz4Rk8Wq2Tn6Lx9Vb3Mc1H",
"invoiceId": "Iv7Hn2Qx5Rt8Kw3Lp6Zb9D",
"applicationType": "PrePayment",
"name": "ServiceFailure",
"amount": 300.00,
"currency": "USD",
"reason": "ServiceFailure",
"status": "Pending",
"amountApplied": 0.00,
"amountRemaining": 300.00,
"pendingRefundAmount": 0.00,
"entries": [],
"events": { "createdAt": "2026-10-12T15:04:11Z" }
}| Field | Required | What it does |
|---|---|---|
payerId | Yes | The Payer you're crediting. |
amount, currency | Yes | The credit amount. It applies only to invoices in this currency. |
reason | Yes | ServiceFailure, Goodwill, Acquisition, Succession, OverCollection, UnauthorizedDeduction, Duplicate, Dispute, Promotional, or Other. |
invoiceId | No | The invoice you issue the credit against. Leave it out to create a balance. |
disposition | When the invoice is paid | Refund, Balance, or OffPlatform. See Credit a paid invoice. |
name, memo | No | A display name (defaults to the reason) and your note. |
lineItems | No | A breakdown of the credit. Each line needs a description and an amount, and the line amounts must add up to amount. documentLineItemId ties a line to an invoice line. |
startDate, endDate | No | When the credit can apply. See Start and end dates. |
priority | No | Order among several credits. Lower numbers apply first. |
externalId, metadata | No | Your own IDs. externalId is up to 128 characters, and a duplicate returns 409. |
Until the payer pays, the invoice shows the credit in pendingCreditAmount, a preview of what will apply. When the payment runs, Wingspan applies the credit and collects $900. The reduced amount applies however the payer pays through Wingspan, and when you record an off-platform payment on this invoice.
// GET /v3/payments/invoices/Iv7Hn2Qx5Rt8Kw3Lp6Zb9D (trimmed)
{
"id": "Iv7Hn2Qx5Rt8Kw3Lp6Zb9D",
"status": "Paid",
"amount": 1200.00,
"creditIds": ["Cr9Kd2Wm6Qx4Tn8Lv1Bz5R"],
"creditedAmount": 300.00,
"pendingCreditAmount": 0.00
}// GET /v3/payments/credits/Cr9Kd2Wm6Qx4Tn8Lv1Bz5R (trimmed)
{
"id": "Cr9Kd2Wm6Qx4Tn8Lv1Bz5R",
"status": "Complete",
"completionReason": "FullyApplied",
"amountApplied": 300.00,
"amountRemaining": 0.00,
"entries": [
{
"id": "En5Tq8Wz1Rk4Lm7Vx2Nb6H",
"type": "Applied",
"amount": 300.00,
"documentId": "Iv7Hn2Qx5Rt8Kw3Lp6Zb9D",
"metadata": {},
"events": { "occurredAt": "2026-10-15T14:02:37Z" },
"actors": { "occurredBy": { "type": "System", "id": "AdjustmentApplication" } }
}
]
}If a credit is bigger than what's left on the invoice, Wingspan applies the part that fits. Without a disposition, it closes the rest with a Closed entry (reason: ShortApplied) and the credit completes with completionReason: ShortApplied. With disposition: Balance, the rest stays available for the payer's later invoices.
A failed or returned payment leaves the credit applied. When the payer pays again, Wingspan collects the reduced amount.
Credit a paid invoice
Once an invoice is paid, disposition tells Wingspan what to do with the credit. A credit on a paid invoice without one returns 422.
disposition | What happens |
|---|---|
Refund | Wingspan refunds the amount to the payer through the invoice's refund. |
Balance | The amount goes on the payer's balance and applies to its next invoices. |
OffPlatform | You returned the money outside Wingspan, for example by check. Wingspan writes a Closed entry with reason: OffPlatform and completes the credit with completionReason: OffPlatform. No money moves. |
curl -X POST "https://api.wingspan.app/v3/payments/credits" \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"payerId": "Pz4Rk8Wq2Tn6Lx9Vb3Mc1H",
"invoiceId": "Iv7Hn2Qx5Rt8Kw3Lp6Zb9D",
"amount": 300.00,
"currency": "USD",
"reason": "ServiceFailure",
"disposition": "Refund"
}'A refund moves money, so disposition: Refund has the same checks as refunding the invoice:
- It needs the
payments.invoice-refund:writescope. If you call with a person's login session, it needs a recent MFA step-up (HighRiskWriteAction); without it the call returns403withcode: StepUpMfaRequired. A backend calling with an API key has no session to step up. - The payer must have paid the invoice through Wingspan. For an invoice paid off-platform, use
OffPlatformwhen you pay the money back yourself, orBalance. - The amount can't exceed the cash still refundable on the invoice. Credits and direct refunds share that limit, so the same money can't go back twice. Above it, you get
422with the field reasonAmountExceedsRefundable. - While the refund runs, Wingspan holds the amount in
pendingRefundAmount, and no other invoice can use it. When the refund settles, Wingspan writes aRefundedentry and, if nothing is left on the credit, completes it withcompletionReason: Refunded. If the refund fails, Wingspan releases the hold and the amount can apply to invoices again.
Carry a credit balance
Leave out invoiceId to give the payer a balance. Wingspan applies it to the payer's next invoices until it's used up. Wingspan applies a balance when the payer pays through Wingspan. A credit that targets an invoice also applies when you record an off-platform payment on it.
curl -X POST "https://api.wingspan.app/v3/payments/credits" \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"payerId": "Pz4Rk8Wq2Tn6Lx9Vb3Mc1H",
"amount": 300.00,
"currency": "USD",
"reason": "Goodwill",
"memo": "Credit for the September scheduling issue"
}'The payer's next invoice is $200, and the one after it is $150:
| Step | Applied | Left on the credit | Payer pays |
|---|---|---|---|
| Credit created | $0 | $300 | |
| $200 invoice paid | $200 | $100 | $0 |
| $150 invoice paid | $100 | $0 | $50 |
After the first invoice, the credit is PartiallyApplied. After the second, it's Complete with completionReason: FullyApplied, and entries holds one Applied entry for each invoice: $200 on the first and $100 on the second.
A balance applies only within one relationship and one currency. It reduces invoices from the Account that created it, to the same Payer, in the same currency. Credits from your US Account never reach invoices from your Canadian Account, and a CAD credit never applies to a USD invoice. Each Payer record has its own balance, so if you bill a group's locations as separate payers, a credit for one location stays with that location.
Read a payer's balance with GET /v3/payments/payers/{payerId}/credit-balance:
{
"accountId": "Ac1Hm5Wq8Rz2Kt6Lx9Vn3B",
"asOf": "2026-10-20T09:00:00Z",
"data": [
{ "currency": "USD", "available": 100.00, "notStarted": 0.00 }
]
}available is what the payer's started, unexpired credits have left, minus any refunds in progress. notStarted covers credits whose startDate is still ahead.
Order of application
When several credits can apply to the same invoice, Wingspan uses them in this order:
- Credits that target the invoice, then balance credits.
- Within each group, the lowest
priorityfirst. A credit without a priority counts as 0. - Then the earliest
endDate. Credits without an end date go last. - Then the oldest credit.
Wingspan caps each application at the invoice's unpaid amount and at what's left on the credit, so two credits never take an invoice below zero. Each application is atomic for that credit and that invoice. Two invoices paid at the same moment can't spend the same balance, and retrying a payment doesn't apply a credit twice. pendingCreditAmount uses the same order and caps, but it's a preview and doesn't hold the amount.
Start and end dates
startDate and endDate are plain dates in UTC, and both are inclusive. A credit can apply from the start of its startDate through the end of its endDate.
After the endDate, Wingspan expires whatever is left. It writes an Expired entry, and the credit completes with completionReason: Expired. Expiry never touches an amount held for a refund in progress. If the endDate passes while a refund is running, Wingspan expires only the unreserved part, then expires whatever is left once the refund settles or fails.
A $300 Promotional credit runs from October 10 to October 31. The payer uses $100 on October 20. On November 1, Wingspan expires the other $200:
// (trimmed)
{
"reason": "Promotional",
"startDate": "2026-10-10",
"endDate": "2026-10-31",
"status": "Complete",
"completionReason": "Expired",
"entries": [
{ "type": "Applied", "amount": 100.00, "documentId": "Iv2Bq6Wn9Kz3Tr5Lx8Mh1P" },
{ "type": "Expired", "amount": 200.00 }
]
}Cancelled and refunded invoices
When you cancel an invoice, or refund all of the cash collected on it, Wingspan gives back the credit used on that invoice, once.
- If the credit is still open, Wingspan adds an
Unappliedentry and the amount is available again. A $500 balance with $300 applied to a cancelled invoice goes back to $500 remaining andPending. - If the credit is
Complete, it stays as it is. Wingspan creates a new balance credit for the amount that was applied to that invoice, linked to the original bysourceAdjustmentId, with the same reason, currency, andendDate. If thatendDatehas already passed, the new credit expires right away.
The restored amount can't apply to the cancelled or refunded invoice again. A partial refund doesn't give back credit. If an invoice is both cancelled and refunded, Wingspan restores the amount only once.
Close or refund what's left
Call POST /v3/payments/credits/{creditId}/complete to finish a credit early. Send remainderDisposition and, optionally, an amount. Without an amount, it uses everything that's available. With one, Wingspan processes only that amount and the rest stays open. An amount above what's available returns 422. The credit completes only when nothing is left and no refund is in progress.
remainderDisposition | What happens |
|---|---|
Refund | Refunds the amount to the payer. The credit needs a paid invoice as its source, and the same scope, multi-factor (login sessions only), and refund-limit checks apply. |
OffPlatform | Records that you settled it outside Wingspan. Writes a Closed entry with reason: OffPlatform. |
Close | Closes the rest with no money movement. Writes a Closed entry with reason: ClosedEarly. |
curl -X POST "https://api.wingspan.app/v3/payments/credits/Cr3Vn7Lq2Wk9Tz5Rb8Hx4M/complete" \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Idempotency-Key: close-goodwill-Cr3Vn7Lq2Wk9Tz5Rb8Hx4M" \
-H "If-Match: $ETAG" \
-H "Content-Type: application/json" \
-d '{ "remainderDisposition": "Close" }'This call always needs an Idempotency-Key. With a person's login session it also needs a recent MFA step-up (HighRiskWriteAction). A backend calling with an API key has no session to step up. Send the ETag from your last read in If-Match, or * to accept whatever version is current. If the credit changed since then, you get 412 with the current ETag. Read the credit again, decide the amount again, and retry with the new ETag and a new Idempotency-Key.
Change or delete a credit
PATCH /v3/payments/credits/{creditId} changes name, memo, externalId, metadata, priority, amount, startDate, and endDate. Once a credit has an entry, its amount and dates are locked, and a Complete credit can't change. An amount change can't drop below any amount held for a refund in progress. Send If-Match here too.
DELETE /v3/payments/credits/{creditId} works only while the credit is Pending with no entries and no refund in progress. Otherwise it returns 409. Use /complete instead.
Audit trail
Every credit records what happened to it:
| Field | What it records |
|---|---|
number | The credit note number, such as CR-000123. The payer sees the same number. |
reason, memo | Why you issued it. |
invoiceId, lineItems | The invoice you issued it against and, optionally, which invoice lines it covers. |
externalId, metadata | Your IDs, such as a job ID or the person who approved the credit. |
actors, events | Who created, last updated, and completed it, and when. |
entries | Every Applied, Unapplied, Refunded, Expired, and Closed amount, with the invoice, time, and actor. |
completionReason | FullyApplied, ShortApplied, ClosedEarly, Expired, Refunded, or OffPlatform. |
Entries are append-only. A reversal is a new Unapplied entry, and earlier entries never change. Each entry's actors.occurredBy has a type (Person, ServiceAccount, or System) and an id. Wingspan's own actions show as AdjustmentApplication, AdjustmentExpiry, or AdjustmentRestoration. If one service account makes all of your API calls, store the person who approved each credit in metadata. It holds up to 50 string keys, with values up to 500 characters.
GET /v3/payments/credits/{creditId}/pdf redirects to the credit note PDF. The link expires within five minutes.
On the invoice, creditIds lists the credits applied to it, creditedAmount is the net amount applied, and pendingCreditAmount is the preview. The invoice's amountDetails covers cash only, so a credit never shows up as a payment.
Tax on a credit
Wingspan stores the taxAmount you send and doesn't calculate it. If a credited amount includes tax, split it in lineItems with taxAmount so your tax records reverse the right amount. taxAmount is part of the line's amount, and the line amounts still add up to the credit's amount.
Webhooks
| Event | When |
|---|---|
Credit.Created | You create a credit, a recurring credit creates one, or Wingspan restores one. |
Credit.Updated | The credit's fields change. |
Credit.Applied | An amount applies to an invoice. |
Credit.Unapplied | Wingspan gives back an applied amount. |
Credit.Completed | The credit finishes. completionReason says why, including Expired. |
Credit.Applied and Credit.Unapplied carry adjustmentId, entryId, invoiceId, amount, and currency in context. A payer linked to a Wingspan Account gets the matching Deduction.* events for the same record, with their own event ids. Refunds also fire the invoice refund events, such as Invoice.RefundCompleted.
Reconcile credits
- Store each credit's
id,number, and everyentries[].id. Entry ids never change. - Dedupe events on the event
id, and match them to your records withcontext.adjustmentIdandcontext.entryId. If your endpoint was down, recover missed events from the event log. - Rebuild any credit from its ledger.
amountAppliedis applied minus unapplied.amountRemainingisamountminus applied, refunded, expired, and closed. What a credit can still apply isamountRemainingminuspendingRefundAmount. - List credits with
GET /v3/payments/credits, filtered bypayerId,invoiceId,status,externalId, or up to 100ids. - Invoice data exports include
creditIdsandcreditedAmount.
Credit every invoice by rule
A recurring credit creates a credit on a payer's invoices, such as 5% off each one. Use targets to choose which invoices qualify. Create it with POST /v3/payments/recurring-credits:
{
"payerId": "Pz4Rk8Wq2Tn6Lx9Vb3Mc1H",
"currency": "USD",
"creditTemplate": {
"name": "Volume discount",
"reason": "Promotional",
"calculation": { "percentage": 5, "percentageBasis": "Gross" }
}
}Wingspan creates exactly one credit per invoice, and each one behaves like any other credit. Each generated credit fires Credit.Created, and the rule fires RecurringCredit.OccurrenceGenerated with creditId and invoiceId in context. Pause, resume, or cancel the rule with its /pause, /resume, and /cancel actions. For one-off rules, such as a cancellation policy in your own system, create the credit directly when the rule fires.
Errors
| Status | When |
|---|---|
401 | The token is missing or invalid. |
403 | The scope is missing, the payer tried to change your credit, or a refund from a login session lacks a recent MFA step-up (StepUpMfaRequired), or the refund scope is missing. |
404 | The credit doesn't exist, or the payer isn't one of yours. |
409 | Duplicate externalId, an Idempotency-Key reused with a different body, a change to a Complete credit, or a delete after entries exist or while a refund is in progress. |
412 | Your If-Match is out of date. |
422 | The amount is zero or less, line items don't add up, endDate is before startDate, reason is missing, a paid invoice has no disposition, Refund or OffPlatform targets an unpaid invoice or no invoice, a refund exceeds the refundable cash (AmountExceedsRefundable), the currency doesn't match the invoice, an amount has more decimals than the currency allows, or a /complete amount is above what's available. |
Related pages
Updated 10 days ago