Recurring invoices
Bill a client on a schedule with a V3 RecurringInvoice, then pause, resume, generate early, preview, or cancel the series.
A recurring invoice bills the same client on a schedule, such as a monthly retainer, without you
creating each invoice by hand. This page shows how to set one up and manage the series.
In V1 you checked Repeat this invoice automatically on an invoice, and the API used invoice
templates. In V3 the series is its own resource, RecurringInvoice. Each period it creates an
ordinary invoice that you track like any other.
How it works
A RecurringInvoice has three parts:
- A schedule (
schedule) that says when each invoice is created. - An invoice template (
invoiceTemplate) with the line items and settings copied onto each new
invoice. - Switches for what happens to each new invoice:
shouldAutoSendopens and emails it, and
automaticCollectionModecontrols whether Wingspan collects it automatically. See
Collect each invoice automatically.
Generation only moves forward. A start date in the past sets the rhythm of the schedule, but the
first invoice is created at the next future date. Resuming a paused series doesn't create the
invoices it skipped.
Statuses
| Status | Meaning | How it gets there |
|---|---|---|
Draft | Saved but not running. | Create with "status": "Draft" |
Active | Creating invoices on schedule. | Create (the default), resume, or a PATCH from Draft |
Paused | Not creating invoices. The schedule is kept. | POST .../pause |
Completed | The end condition was reached. | Wingspan, automatically |
Cancelled | Stopped for good. Can't be resumed. | POST .../cancel |
Completed only happens for a series with an end date or a number of occurrences.
Create a recurring invoice
Call POST /v3/payments/recurring-invoices.
payerId, currency, schedule, and invoiceTemplate are required, and the template needs
paymentTermsDays. This example bills Northwind Staffing a 2,500.00 USD retainer on the first of
each month for 12 months, due 15 days after each invoice is issued.
curl -X POST https://api.wingspan.app/v3/payments/recurring-invoices \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"payerId": "Pq7Lm2Xv9RtK4sWb8NcY3d",
"externalId": "RETAINER-NW-2026",
"currency": "USD",
"schedule": {
"frequency": "Monthly",
"interval": 1,
"dayOfMonth": 1,
"anchorDate": "2026-11-01",
"timezone": "America/Chicago",
"endCondition": { "type": "AfterOccurrences", "maxOccurrences": 12 }
},
"shouldAutoSend": true,
"invoiceTemplate": {
"paymentTermsDays": 15,
"invoiceNumberPrefix": "NW-",
"lineItems": [
{ "description": "Monthly design retainer", "type": "Services", "totalCost": 2500.00 }
],
"acceptedPaymentMethods": ["Ach"],
"notificationPreferences": { "shouldSendInvoice": true, "shouldSendReminders": true }
}
}'// 201 Created (trimmed)
{
"id": "Nr2Kx6Lq9Vz3Wt7Bm1Pc5d",
"externalId": "RETAINER-NW-2026",
"payerId": "Pq7Lm2Xv9RtK4sWb8NcY3d",
"status": "Active",
"currency": "USD",
"nextOccurrenceDate": "2026-11-01",
"occurrencesGenerated": 0,
"isAutoSendEnabled": true,
"isAutoPayEnabled": false,
"events": { "createdAt": "2026-09-24T16:04:55Z" }
}The response carries an ETag. Send it as If-Match on later updates and actions. See
Concurrency and ETags.
To bill against one engagement rather than the Payer's default, add payerEngagementId. Wingspan
returns 404 if the Payer or engagement isn't yours.
The schedule
| Field | Notes |
|---|---|
frequency | Daily, Weekly, SemiMonthly, Monthly, or Annual. |
interval | Every N periods. Monthly with interval: 3 is quarterly, and Weekly with interval: 2 is every two weeks. Defaults to 1. |
anchorDate | The first or reference date. Required with frequency. |
dayOfWeek | Required for Weekly, for example Friday. |
dayOfMonth | For Monthly and Annual. In short months it moves to the last day of the month. |
daysOfMonth | For SemiMonthly only: exactly two days, such as [1, 15]. |
timezone | IANA timezone that dates are evaluated in. It can't change after the first invoice is created. |
businessDayAdjustment | PreviousBusinessDay, NextBusinessDay, or None (default). |
holidayCalendar | USFederalReserve or None (default). |
prorationPolicy | None (default) or ByDays, for starting, pausing, or cancelling mid-period. |
endCondition | { "type": "Never" }, { "type": "OnDate", "endDate": "2027-10-31" }, or { "type": "AfterOccurrences", "maxOccurrences": 12 }. maxOccurrences can be 1 to 30. |
scheduleDates | An explicit list of dates, for irregular schedules. You can use it instead of frequency. |
The invoice template
invoiceTemplate takes most of the fields you'd put on a one-off invoice. See
Components of an invoice for what each does.
| Field | Notes |
|---|---|
paymentTermsDays | Required. Each invoice's due date is its issue date plus this many days, from 0 to 180. 0 means due on receipt. |
invoiceNumberPrefix | Up to 20 characters, such as NW-. Wingspan numbers the invoices. |
lineItems | The charges. On a template, the line-item type field is type, not lineItemType. |
splits | Collaborator splits copied onto each invoice. |
acceptedPaymentMethods, lateFeeHandling, creditFeeHandling, notificationPreferences, notes, metadata | The same as on an invoice. Late fees are measured from each generated invoice's own due date. |
A template has no fixed due date or invoice number, because each generated invoice gets its own.
Manage the series
All of these are under /v3/payments/recurring-invoices/{recurringInvoiceId} and accept If-Match
and Idempotency-Key.
| Call | What it does |
|---|---|
PATCH | Change the schedule, invoiceTemplate, shouldAutoSend, automaticCollectionMode, or metadata. Template fields you send are merged in, and lineItems replaces the whole list. Changes apply to future invoices only. |
POST .../pause | Stop creating invoices and keep the schedule. |
POST .../resume | Start again from now. Skipped periods aren't billed. |
POST .../cancel | Stop for good. An optional reason (up to 1,000 characters) is kept on the record. |
POST .../generate-now | Create the next scheduled invoice right away. Returns 201 with the new Invoice and moves the next date forward. |
GET .../upcoming-occurrences | Preview upcoming dates, with occurrenceDate, resolvedDueDate, and sequenceNumber. Nothing is created. Use page[size] to choose how many. |
DELETE | Delete the series. Invoices it already created aren't affected. |
To start a Draft series, PATCH it with { "status": "Active" }. That's the only status change
PATCH accepts. Use the action calls above for everything else.
A stale If-Match returns 412. An action that doesn't fit the current status, such as resuming a
cancelled series, returns 409.
Collect each invoice automatically
Set automaticCollectionMode on create or PATCH to decide whether Wingspan collects each
generated invoice without a pay call:
| Value | Effect |
|---|---|
Enabled | Collect this series' invoices automatically. |
Disabled | Don't collect this series' invoices automatically, even if the engagement or Payer is set to. |
Inherit | Clear this series' setting and follow the engagement, then the Payer. |
Leaving the field out of a PATCH keeps the current value. Changes apply to payments started after
the change. Collection uses the engagement's installed funding authorization, so nothing is collected
until the engagement has one. For bank debit, that's a bank account with an active Recurring
mandate. See
Automatic collection for how the Payer and engagement
settings work and how to get the authorization.
isAutoPayEnabled on the response is an older view of the same preference. It can't tell
Inherit apart from Disabled, so read automaticCollectionMode instead. The older shouldAutoPay
switch is still accepted, but new integrations should use automaticCollectionMode.
Find the invoices a series created
Each generated invoice carries recurringInvoiceId, so you can match it to its series. The series
shows occurrencesGenerated, lastOccurrenceDate, and nextOccurrenceDate. Once the series ends,
nextOccurrenceDate is null.
List series with
GET /v3/payments/recurring-invoices,
filtered by filter[payerId][eq], filter[externalId][eq], or filter[status][eq], and sorted with
sort[createdAt]=desc. Send the same sort on every page.
Webhooks
RecurringInvoice webhook events aren't available to subscribe to yet. Read the series with
GET /v3/payments/recurring-invoices/{recurringInvoiceId} to track it, and follow each generated
invoice through its own invoice events.
Related pages
- Create an invoice
- Collect payment
- Recurring payables, the payer-side equivalent
- Deductions and credits
Updated 10 days ago