Submit invoices via the Recovery API

Submit a failed invoice to Recurly Recover for automated retry collection, read the response, and stop retries when a payment is collected or abandoned.

The Recovery API is the entry point to Recurly Recover. A single authenticated request submits a failed invoice for collection — Recurly creates the account, opens a past-due invoice, records the failed transaction, and starts retrying on the retry window you assign. This guide covers submitting an invoice, reading the response, and stopping retries when collection finishes elsewhere.

Prerequisites

  • An active Recurly Recover account with an API key generated — see Getting started.
  • At least one retry window (dunning campaign) configured.
  • A reusable gateway token for each customer's payment method from Stripe or Braintree.
Warning Pause your own retry logic before submitting an invoice. Running parallel retries on the same payment method risks double-charging your customer.

Submit a failed invoice

Send a POST request to /invoices/recovery. A successful call creates a Recurly Account, a past-due charge invoice, and an initial failed transaction — and immediately begins the retry schedule.

Recover authenticates with HTTP Basic Auth: pass your API key as the username with an empty password.

curl -u YOUR_RECOVER_API_KEY: \
  -X POST https://v3.recurly.com/invoices/recovery \
  -H "Content-Type: application/json" \
  --data @recovery-request.json

Request body

{
  "currency": "str",
  "due_at": "2019-08-24T14:15:22Z",
  "po_number": "string",
  "external_recovery_eligible": true,
  "account": {
    "address": {
      "phone": "string",
      "street1": "string",
      "street2": "string",
      "city": "string",
      "region": "string",
      "postal_code": "string",
      "country": "string"
    },
    "billing_infos": [
      {
        "first_name": "string",
        "last_name": "string",
        "company": "string",
        "address": {
          "phone": "string",
          "street1": "string",
          "street2": "string",
          "city": "string",
          "region": "string",
          "postal_code": "string",
          "country": "string"
        },
        "ip_address": "string",
        "gateway_code": "string",
        "primary_payment_method": true,
        "backup_payment_method": true,
        "payment_gateway_references": [
          {
            "token": "string"
          }
        ],
        "network_transaction_id": "string",
        "transactions": [
          {
            "gateway_error_code": "string",
            "merchant_advice_code": "st",
            "attempted_collection_date": "2019-08-24T14:15:22Z"
          }
        ]
      }
    ],
    "code": "string",
    "email": "[email protected]",
    "custom_fields": [
      {
        "name": "string",
        "value": "string"
      }
    ],
    "dunning_campaign_id": "string"
  },
  "line_items": [
    {
      "tax": 0,
      "custom_fields": [
        {
          "name": "string",
          "value": "string"
        }
      ],
      "harmonized_system_code": "string",
      "product_code": "string",
      "quantity": 1,
      "description": "string",
      "unit_amount": 0
    }
  ]
}

If you are using Stripe, the Customer ID and Payment Method IDs are two-part tokens, and you will provide the payment_gateway_references array as follows:

"payment_gateway_references": [
          {
            "token": "stripe-customer-id", // ex: cus_xxxxxxxx
            "reference_type":"stripe_customer"
          },
          {
            "token": "stripe-pm-id", // ex: pm_xxxxxxxx
            "reference_type":"stripe_payment_method"
          }
        ],

If you are providing multiple payment methods in a single account for this Recover invoice, your payload will look like below. The example shows 5 payment methods associated with the invoice, 3 of which were used on a previous attempt.

The example shows formats for tokens mixing between Stripe formatting and non-Stripe formatting.

{
  "currency": "USD",
  "po_number": "NNNN",
  "due_at": "YYYY-MM-DDTHH:MM:SS.MSZ", // Date and Time 
  "account": {
    "code": "account-code", // Account code
    "dunning_campaign_id": "{{dunning_campaign_id}}", // Dunning Campaign ID
    "billing_infos": [ 
// each billing info with gateway code, and the transaction attempt, error code, attempted date, and MAC code
      {
        "gateway_code": "{{gateway_code}}", // Gateway code -- must have access to the gateway token provided
        "primary_payment_method": true, // Wallet Primary indicator
        "backup_payment_method": false,
        "payment_gateway_references": [ // Stripe Token Format
          {
            "token": "pm_XXXXXXXXXXX",
            "reference_type": "stripe_payment_method"
          },
          {
            "token": "cus_XXXXXXXXXXX",
            "reference_type": "stripe_customer"
          }
        ],
        "transactions": [
            {
                "gateway_error_code": "gateway-responsed-code-value", // The actual gateway response code returned in your integration
                "attempted_collection_date": "YYYY-MM-DDTHH:MM:SS.MSZ",
                "merchant_advice_code": "NN"
            }
        ]
      },
      {
        "gateway_code": "{{gateway_code}}", // Gateway Code 
        "primary_payment_method": false, 
        "backup_payment_method": true, // Wallet Backup Indicator
        "payment_gateway_references": [ // Non-Stripe Token Example
          {
            "token": "XXXXXXXXXXX"
          }
        ],
        "network_transaction_id": "string", // Example token that requires NTID
        "transactions": [
            {
                "gateway_error_code": "gateway-responsed-code-value", // The actual gateway response code returned in your integration
                "attempted_collection_date": "YYYY-MM-DDTHH:MM:SS.MSZ",
                "merchant_advice_code": "NN"
            }
        ]
      },
      {
        "gateway_code": "{{gateway_code}}",
        "primary_payment_method": false,
        "backup_payment_method": false,
        "payment_gateway_references": [
          {
            "token": "XXXXXXXXXXX" // Example token that does not required separate NTID
          }
        ],
        "transactions": [
            {
                "gateway_error_code": "gateway-responsed-code-value", 
                "attempted_collection_date": "YYYY-MM-DDTHH:MM:SS.MSZ",
                "merchant_advice_code": "NN"
            }
        ]
      },
      {
        "gateway_code": "{{gateway_code}}",
        "primary_payment_method": false,
        "backup_payment_method": false,
        "payment_gateway_references": [
          {
            "token": "pm_XXXXXXXXXXX",
            "reference_type": "stripe_payment_method"
          },
          {
            "token": "cus_XXXXXXXXXXX",
            "reference_type": "stripe_customer"
          }
        ]
      },
      {
        "gateway_code": "{{gateway_code}}",
        "primary_payment_method": false,
        "backup_payment_method": false,
        "payment_gateway_references": [
          {
            "token": "XXXXXXXXXXX"
          }
        ]
      }
    ],
    "email": "[email protected]"
  },
  "line_items": [
    {
      "description": "Description of Invoice", // Overwritten when using Vindicia
      "unit_amount": 9.99
    }
  ],
  "external_recovery_eligible": true,
  "transaction_descriptor_suffix": "Descriptor Suffix" // New Descriptor Field (Suffix)
}

Fields that drive retry behavior

The full schema is documented in the API reference. These are the fields that determine how Recover collects:

FieldWhat it does
account.codeYour unique identifier for the account. Reusing a code that already exists returns an error — each call creates one account with one invoice.
account.dunning_campaign_idThe retry window Recover follows for this invoice. Assign a different campaign per request to test strategies across segments.
gateway_codeRoutes the transaction to the correct gateway connection.
payment_gateway_referencesThe reusable gateway token and its reference_type.
transactionsPrior failed attempt history — gateway_error_code, merchant_advice_code, and attempted_collection_date — used to calculate the first retry date.

Handle the response

A 201 response confirms that Recurly created the account and started the retry process. Save the id of the returned charge_invoice — you'll need it to stop retries later.

{
  "object": "string",
  "charge_invoice": {
    "id": "string",
    "uuid": "string",
    "object": "string",
    "type": "charge",
    "origin": "carryforward_credit",
    "state": "open",
    "account": {
      "id": "string",
      "object": "string",
      "code": "string",
      "email": "[email protected]",
      "first_name": "string",
      "last_name": "string",
      "company": "string",
      "parent_account_id": "string",
      "bill_to": "parent",
      "dunning_campaign_id": "string"
    },
    "billing_info_id": "string",
    "subscription_ids": ["string"],
    "previous_invoice_id": "string",
    "number": "string",
    "collection_method": "automatic",
    "po_number": "string",
    "net_terms": 0,
    "net_terms_type": "net",
    "currency": "str",
    "discount": 0,
    "subtotal": 0,
    "subtotal_after_discount": 0,
    "tax": 0,
    "total": 0,
    "refundable_amount": 0,
    "paid": 0,
    "balance": 0,
    "dunning_campaign_id": "string",
    "due_at": "2019-08-24T14:15:22Z",
    "closed_at": "2019-08-24T14:15:22Z",
    "created_at": "2019-08-24T14:15:22Z",
    "updated_at": "2019-08-24T14:15:22Z"
  }
}
Note The response doesn't include the next scheduled retry date. Track retry progress through webhook notifications instead.

Stop retries

While an invoice is in a past_due state, you can cancel all future retry attempts at any time. Use the invoice id returned in the original response. Once marked, Recover makes no further attempts on that invoice.

Mark as paid

Use when the payment was collected outside of Recurly.

curl -u YOUR_RECOVER_API_KEY: \
  -X PUT https://v3.recurly.com/invoices/{invoice_id}/mark_successful

Mark as failed

Use when you want to abandon collection.

curl -u YOUR_RECOVER_API_KEY: \
  -X PUT https://v3.recurly.com/invoices/{invoice_id}/mark_failed

Payment method wallet

When the Wallet feature is enabled, you can designate payment methods as primary or backup in the same request. Set primary_payment_method or backup_payment_method to true on each entry in billing_infos. You can submit multiple payment methods, but only one can be marked as primary.

Supported gateways

Recover currently supports the following gateways with reusable gateway tokens:

GatewayToken support
StripeReusable gateway tokens)
BraintreeReusable gateway tokens

Error handling

Every call creates exactly one account with one invoice. Submitting a request with an account.code that already exists returns an error rather than creating a duplicate — generate a unique code per invoice, or mark the existing invoice as paid or failed before resubmitting.

What's next

  • Recovery API reference — the complete endpoint and field schema
  • Webhooks — track retry progress and confirm the final outcome of each invoice
  • Getting started — connect a gateway, set up webhooks, and generate your API key

Did this page help you?