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 toUse
Change the invoice itself so the payer sees corrected line itemsUpdate the invoice while it's Created or Opened
Keep the invoice as issued and lower what the payer owes with a numbered credit noteA credit with invoiceId
Give money back on a paid invoiceA credit with invoiceId and disposition: Refund, or refund the invoice
Keep the amount for the payer's later invoicesA 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" }
}
FieldRequiredWhat it does
payerIdYesThe Payer you're crediting.
amount, currencyYesThe credit amount. It applies only to invoices in this currency.
reasonYesServiceFailure, Goodwill, Acquisition, Succession, OverCollection, UnauthorizedDeduction, Duplicate, Dispute, Promotional, or Other.
invoiceIdNoThe invoice you issue the credit against. Leave it out to create a balance.
dispositionWhen the invoice is paidRefund, Balance, or OffPlatform. See Credit a paid invoice.
name, memoNoA display name (defaults to the reason) and your note.
lineItemsNoA 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, endDateNoWhen the credit can apply. See Start and end dates.
priorityNoOrder among several credits. Lower numbers apply first.
externalId, metadataNoYour 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.

dispositionWhat happens
RefundWingspan refunds the amount to the payer through the invoice's refund.
BalanceThe amount goes on the payer's balance and applies to its next invoices.
OffPlatformYou 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:write scope. If you call with a person's login session, it needs a recent MFA step-up (HighRiskWriteAction); without it the call returns 403 with code: 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 OffPlatform when you pay the money back yourself, or Balance.
  • 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 422 with the field reason AmountExceedsRefundable.
  • While the refund runs, Wingspan holds the amount in pendingRefundAmount, and no other invoice can use it. When the refund settles, Wingspan writes a Refunded entry and, if nothing is left on the credit, completes it with completionReason: 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:

StepAppliedLeft on the creditPayer 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:

  1. Credits that target the invoice, then balance credits.
  2. Within each group, the lowest priority first. A credit without a priority counts as 0.
  3. Then the earliest endDate. Credits without an end date go last.
  4. 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 Unapplied entry and the amount is available again. A $500 balance with $300 applied to a cancelled invoice goes back to $500 remaining and Pending.
  • 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 by sourceAdjustmentId, with the same reason, currency, and endDate. If that endDate has 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.

remainderDispositionWhat happens
RefundRefunds 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.
OffPlatformRecords that you settled it outside Wingspan. Writes a Closed entry with reason: OffPlatform.
CloseCloses 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:

FieldWhat it records
numberThe credit note number, such as CR-000123. The payer sees the same number.
reason, memoWhy you issued it.
invoiceId, lineItemsThe invoice you issued it against and, optionally, which invoice lines it covers.
externalId, metadataYour IDs, such as a job ID or the person who approved the credit.
actors, eventsWho created, last updated, and completed it, and when.
entriesEvery Applied, Unapplied, Refunded, Expired, and Closed amount, with the invoice, time, and actor.
completionReasonFullyApplied, 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

EventWhen
Credit.CreatedYou create a credit, a recurring credit creates one, or Wingspan restores one.
Credit.UpdatedThe credit's fields change.
Credit.AppliedAn amount applies to an invoice.
Credit.UnappliedWingspan gives back an applied amount.
Credit.CompletedThe 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 every entries[].id. Entry ids never change.
  • Dedupe events on the event id, and match them to your records with context.adjustmentId and context.entryId. If your endpoint was down, recover missed events from the event log.
  • Rebuild any credit from its ledger. amountApplied is applied minus unapplied. amountRemaining is amount minus applied, refunded, expired, and closed. What a credit can still apply is amountRemaining minus pendingRefundAmount.
  • List credits with GET /v3/payments/credits, filtered by payerId, invoiceId, status, externalId, or up to 100 ids.
  • Invoice data exports include creditIds and creditedAmount.

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

StatusWhen
401The token is missing or invalid.
403The 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.
404The credit doesn't exist, or the payer isn't one of yours.
409Duplicate 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.
412Your If-Match is out of date.
422The 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


Did this page help you?