Credits
Lower what a payer owes with a Wingspan V3 credit. Target a credit at one invoice or keep it as a balance for later invoices, 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. Only the Account that created a credit can change it. A credit is created without being applied. Creating one never moves money.
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 | A credit with invoiceId |
| Give money back on a paid invoice | Refund the invoice. A credit doesn't move money. |
| Keep the amount for the payer's later invoices | A credit with no invoiceId |
A credit changes what a payer owes you. It never changes a payable or a payout.
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",
"metadata": { "jobId": "job_55102", "approvedBy": "ops_0042" }
}'// (trimmed)
{
"id": "Cr9Kd2Wm6Qx4Tn8Lv1Bz5R",
"payerId": "Pz4Rk8Wq2Tn6Lx9Vb3Mc1H",
"invoiceId": "Iv7Hn2Qx5Rt8Kw3Lp6Zb9D",
"applicationType": "PrePayment",
"applicationStrategy": "Manual",
"name": "ServiceFailure",
"amount": 300.00,
"currency": "USD",
"reason": "ServiceFailure",
"status": "Pending",
"amountApplied": 0.00,
"amountRemaining": 300.00,
"entries": [],
"metadata": { "jobId": "job_55102", "approvedBy": "ops_0042" }
}The call returns the credit's ETag in a response header. Send it as If-Match on later changes.
| 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 | Why you issued the credit, such as Goodwill, ServiceFailure, or Other. Any non-empty string is accepted. |
invoiceId | No | The invoice you target. Leave it out to create a balance. |
applicationStrategy | No | Manual applies only to its target invoice. Automatic applies at payment, on or after startDate. The default is Manual with an invoiceId and Automatic without one. Automatic with an invoiceId returns 422. |
name, memo | No | A display name (defaults to the reason) and your note. |
startDate | No | A date or date-time from which an Automatic credit can apply. A date-only value means midnight UTC. Ignored for Manual credits. |
metadata | No | Your own string key-value pairs, such as a job ID or the person who approved the credit. |
The target must be one of your invoices in Created, Pending, Opened, or Overdue, with no active or completed payment, for the same payer and currency. The whole credit must fit in what is left on the invoice after other credits that target it. Otherwise the call returns 422. Fields that aren't listed here are rejected.
Until the payer pays, the invoice shows the credit in pendingCreditAmount, a preview of what will apply. amountDueAfterCredits is the invoice's amountDue minus those pending credits, and it is what the next payment through Wingspan collects. When you record an off-platform payment, Wingspan also applies the credits that target the invoice and any eligible Automatic credits.
// 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",
"amountApplied": 300.00,
"amountRemaining": 0.00,
"entries": [
{
"id": "En5Tq8Wz1Rk4Lm7Vx2Nb6H",
"type": "Applied",
"amount": 300.00,
"documentId": "Iv7Hn2Qx5Rt8Kw3Lp6Zb9D",
"occurredAt": "2026-10-15T14:02:37Z",
"occurredBy": "AdjustmentApplication"
}
]
}Target a credit after you create it
To point an untargeted credit at an invoice, call POST /v3/payments/credits/{creditId}/apply with { "invoiceId": "..." }. The credit switches to Manual. The whole available credit must fit the room left on the invoice. This call only sets the target. It doesn't apply any money.
To clear the target, call POST /v3/payments/credits/{creditId}/remove with the same invoiceId. The credit stays Manual. If the credit already has an Applied entry for that invoice, the call returns 409, even if the payment later failed. Both calls take If-Match and an Idempotency-Key.
Carry a credit balance
Leave out invoiceId to give the payer a balance. With the default Automatic strategy, Wingspan applies it to the payer's invoices as they are paid, until it's used up.
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",
"startDate": "2026-10-10"
}'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, 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. 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. The balance leaves out Complete credits.
Close what's left
Call POST /v3/payments/credits/{creditId}/complete to finish a credit early and stop it from applying. Send remainderDisposition: Close and no amount. Wingspan closes the whole unused remainder with a Closed entry (reason: ClosedEarly) and the credit completes with completionReason: ClosedEarly. No money moves. Refund, OffPlatform, and a partial amount aren't supported.
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. It also needs elevated authentication: with a person's login session, 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. Read the credit again and retry with the new ETag and a new Idempotency-Key.
To give cash back to a payer, refund the invoice instead.
Change or delete a credit
PATCH /v3/payments/credits/{creditId} changes name, memo (send null to clear it), metadata, amount, and applicationStrategy.
amountcan change only before the credit has any entry. A credit that targets an invoice must still fit the invoice.applicationStrategycan switch betweenAutomaticandManualonly while the credit has no target. Switching toManualstops automatic application.- A
Completecredit can't change.
Send If-Match with the credit's ETag or *.
DELETE /v3/payments/credits/{creditId} works only for a credit your Account created, while it's Pending with no entries. Otherwise it returns 409. Use /complete instead. DELETE also takes If-Match.
Audit trail
Every credit records what happened to it:
| Field | What it records |
|---|---|
reason, memo, name | Why you issued it. |
invoiceId | The invoice it targets. |
applicationType, applicationStrategy | PrePayment, and whether it applies only to its target (Manual) or at payment (Automatic). |
metadata | Your own values, such as a job ID or the person who approved the credit. |
actors.completedBy, events.completedAt | Who completed the credit, and when. |
entries | Every Applied and Closed amount, with the invoice (documentId), time (occurredAt), and actor (occurredBy). |
completionReason | ClosedEarly, once you close the credit. |
Entries are append-only, and each one has a permanent id. If one service account makes all of your API calls, store the person who approved each credit in metadata.
GET /v3/payments/credits/{creditId}/pdf redirects (302) to a signed credit PDF URL. The link expires within five minutes.
On the invoice, creditIds lists the credits applied to it, creditedAmount is the amount applied, and pendingCreditAmount is the preview. The invoice's amountDetails covers cash only, so a credit never shows up as a payment.
Webhooks
You can subscribe to Credit.Created and Credit.Updated. You can also read a credit with GET /v3/payments/credits/{creditId} or list credits. To see when an invoice paid with a credit applied, subscribe to the invoice events. See Webhooks and events overview.
Reconcile credits
- Store each credit's
idand everyentries[].id. Entry ids never change. - Rebuild any credit from its ledger.
amountAppliedis whatAppliedentries add up to.amountRemainingisamountminus applied and closed amounts. - List credits with
GET /v3/payments/credits, filtered byfilter[payerId][eq],filter[invoiceId][eq], orfilter[status][eq](Pending,PartiallyApplied, orComplete). TheinvoiceIdfilter includes credits that target the invoice and credits already stored on it, and you must own the invoice. The list supportspage[size]andpage[token]. - 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" }
}
}Each generated credit behaves like any other credit. List, read, update, or delete rules at /v3/payments/recurring-credits and /v3/payments/recurring-credits/{recurringCreditId}. Only the Account that created a rule can change it. Pause, resume, or cancel a rule with its /pause, /resume, and /cancel actions. A duplicate externalId on create returns 409 ResourceConflict. For one-off rules, such as a cancellation policy in your own system, create the credit directly when the rule fires.
Errors
| Status | When |
|---|---|
400 | The body has a field that isn't supported, or a value has the wrong type. |
401 | The token is missing or invalid. |
403 | The scope is missing, the payer tried to change your credit, or /complete from a login session lacks a recent MFA step-up (StepUpMfaRequired). |
404 | The credit doesn't exist, or the payer isn't one of yours. |
409 | An Idempotency-Key reused with a different body, a change to a Complete credit, remove after an Applied entry exists, or a delete after entries exist. |
412 | Your If-Match is out of date. |
422 | The amount is zero or less, reason is missing, Automatic is set with an invoiceId, the target invoice isn't eligible or is in another currency, the credit doesn't fit the room left on the invoice, or an amount has more decimals than the currency allows. |
Related pages
Updated 22 minutes ago