# 

Recurring billing here is two fields on a normal transaction, not a separate product. There is no subscription resource, no plan, no customer object, and no recurring endpoint. **You own the schedule**; the gateway records the stored-credential relationship the card networks require and returns the identifiers that link one payment to the next.

Everything goes through `POST /v1/transactions`.

## The fields

| Field | What it does |
|  --- | --- |
| `recurring` | Boolean. Marks the transaction as part of a recurring arrangement |
| `initiator` | `CUSTOMER` when the cardholder is present, `MERCHANT` when you bill without them |
| `recurring_details.sequence` | `INITIAL` for the first payment, `SUBSEQUENT` for every one after |
| `recurring_details.initial_transaction_id` | The `id` of the transaction that began the series |
| `recurring_details.initial_network_transaction_id` | The network's own identifier, when the series began somewhere else |


## Step 1: The initial payment

The first payment establishes the agreement. The cardholder is present and consenting, so it is customer-initiated, and it carries the CVV.

```json 200 application/json
{
  "id": "TRANSACTION-01KEW3C2HQ7V5N8R4TJXW9MDF6",
  "reference_transaction_id": null,
  "type": "SALE",
  "result": "APPROVED",
  "client_id": "CLIENT-01KFDKXMQ637EKEAY410MSQSXB",
  "client_dba": "Acme Jewelry",
  "integrations": [
    "TSYS"
  ],
  "response_code": "00",
  "response_description": "Approved",
  "batch_id": null,
  "is_settled": false,
  "time_created": "2026-07-15T18:05:12Z",
  "amount": 10000,
  "tip": 0,
  "tax_amount": 500,
  "currency": "USD",
  "payment_method": {
    "type": "CARD",
    "truncated_pan": "****-****-****-5439",
    "payment_token": "CARD_TOKEN-01KFDKXMQ637EKEAY410MSQSXB",
    "expiry": {
      "month": "01",
      "year": "2028"
    },
    "first_name": "John",
    "last_name": "Doe",
    "entry_method": "ECOMMERCE",
    "auth_code": "123456",
    "retrieval_reference_number": "000000603079",
    "card_type": "UNKNOWN",
    "card_brand": "Visa",
    "cvv_result_code": "M",
    "avs_result_code": "Y",
    "avs_response": "Exact Match - Street address and postal code match"
  },
  "recurring": true,
  "initiator": "CUSTOMER",
  "recurring_details": {
    "sequence": "INITIAL",
    "initial_transaction_id": null,
    "initial_network_transaction_id": null
  },
  "network_transaction_id": "000000004271005",
  "metadata": {
    "order_id": "ORD-10440",
    "sales_channel": "web",
    "customer_reference": "cust-8891"
  }
}
```

```json 400 application/json
{
  "error_code": "INVALID_REQUEST_DATA",
  "error_message": "The API request contains invalid data.",
  "details": {
    "amount": "Invalid amount",
    "payment_method.pan": "Malformed PAN"
  },
  "source": {
    "system": "TSYS",
    "code": "13",
    "message": "Invalid amount"
  }
}
```

```json 401 application/json
{
  "error_code": "NOT_AUTHENTICATED",
  "error_message": "Access credentials missing or invalid.",
  "details": null,
  "source": {
    "system": null,
    "code": null,
    "message": null
  }
}
```

```json 403 application/json
{
  "error_code": "UNAUTHORIZED_ACTION",
  "error_message": "Requested action is unavailable or forbidden.",
  "details": null,
  "source": {
    "system": null,
    "code": null,
    "message": null
  }
}
```

```json 409 application/json
{
  "error_code": "DUPLICATE_ACTION",
  "error_message": "Duplicate action requested.",
  "details": null,
  "source": {
    "system": "TSYS",
    "code": "12",
    "message": "Invalid transaction"
  }
}
```

```json 500 application/json
{
  "error_code": "SYSTEM_ERROR",
  "error_message": "Internal system error.",
  "details": null,
  "source": {
    "system": "TSYS",
    "code": "96",
    "message": "System malfunction"
  }
}
```

```json 502 application/json
{
  "error_code": "DOWNSTREAM_ERROR",
  "error_message": "Downstream service returned invalid or unexpected response.",
  "details": null,
  "source": {
    "system": "VERICHECK",
    "code": "502",
    "message": "Bad Gateway"
  }
}
```

```json 503 application/json
{
  "error_code": "string",
  "error_message": "string",
  "details": {
    "property1": "string",
    "property2": "string"
  },
  "source": {
    "system": "TSYS",
    "code": "51",
    "message": "Insufficient funds"
  }
}
```

From the response, keep two values:

* **`id`** — quote it as `initial_transaction_id` on every later payment.
* **`network_transaction_id`** — the card network's identifier for the agreement. Store it; you need it if you ever move this series to another gateway.


## Step 2: Every payment after

Later payments are merchant-initiated: you are billing a card without the cardholder present. Send no CVV, because nobody is there to read one.

You can send the card details again, as that example does, or send a [saved token](/docs/payments/save-and-reuse-a-payment-method). Either works; the token is the better choice because it keeps the number out of your systems.

```json 200 application/json
{
  "id": "TRANSACTION-01KEW3D4YMV8Q7T3FNJZ2XPB56",
  "reference_transaction_id": null,
  "type": "SALE",
  "result": "APPROVED",
  "client_id": "CLIENT-01KFDKXMQ637EKEAY410MSQSXB",
  "client_dba": "Acme Jewelry",
  "integrations": [
    "TSYS"
  ],
  "response_code": "00",
  "response_description": "Approved",
  "batch_id": null,
  "is_settled": false,
  "time_created": "2026-08-15T09:00:00Z",
  "amount": 10000,
  "tip": 0,
  "tax_amount": 500,
  "currency": "USD",
  "payment_method": {
    "type": "CARD",
    "truncated_pan": "****-****-****-5439",
    "payment_token": "CARD_TOKEN-01KFDKXMQ637EKEAY410MSQSXB",
    "expiry": {
      "month": "01",
      "year": "2028"
    },
    "first_name": "John",
    "last_name": "Doe",
    "entry_method": "ECOMMERCE",
    "auth_code": "123456",
    "retrieval_reference_number": "000000603078",
    "card_type": "UNKNOWN",
    "card_brand": "Visa",
    "cvv_result_code": null,
    "avs_result_code": "Y",
    "avs_response": "Exact Match - Street address and postal code match"
  },
  "recurring": true,
  "initiator": "MERCHANT",
  "recurring_details": {
    "sequence": "SUBSEQUENT",
    "initial_transaction_id": "TRANSACTION-01KEW3C2HQ7V5N8R4TJXW9MDF6",
    "initial_network_transaction_id": "000000004271005"
  },
  "network_transaction_id": "000000004271006",
  "metadata": {
    "order_id": "ORD-10437",
    "sales_channel": "recurring",
    "customer_reference": "cust-8891"
  }
}
```

```json 400 application/json
{
  "error_code": "INVALID_REQUEST_DATA",
  "error_message": "The API request contains invalid data.",
  "details": {
    "amount": "Invalid amount",
    "payment_method.pan": "Malformed PAN"
  },
  "source": {
    "system": "TSYS",
    "code": "13",
    "message": "Invalid amount"
  }
}
```

```json 401 application/json
{
  "error_code": "NOT_AUTHENTICATED",
  "error_message": "Access credentials missing or invalid.",
  "details": null,
  "source": {
    "system": null,
    "code": null,
    "message": null
  }
}
```

```json 403 application/json
{
  "error_code": "UNAUTHORIZED_ACTION",
  "error_message": "Requested action is unavailable or forbidden.",
  "details": null,
  "source": {
    "system": null,
    "code": null,
    "message": null
  }
}
```

```json 409 application/json
{
  "error_code": "DUPLICATE_ACTION",
  "error_message": "Duplicate action requested.",
  "details": null,
  "source": {
    "system": "TSYS",
    "code": "12",
    "message": "Invalid transaction"
  }
}
```

```json 500 application/json
{
  "error_code": "SYSTEM_ERROR",
  "error_message": "Internal system error.",
  "details": null,
  "source": {
    "system": "TSYS",
    "code": "96",
    "message": "System malfunction"
  }
}
```

```json 502 application/json
{
  "error_code": "DOWNSTREAM_ERROR",
  "error_message": "Downstream service returned invalid or unexpected response.",
  "details": null,
  "source": {
    "system": "VERICHECK",
    "code": "502",
    "message": "Bad Gateway"
  }
}
```

```json 503 application/json
{
  "error_code": "string",
  "error_message": "string",
  "details": {
    "property1": "string",
    "property2": "string"
  },
  "source": {
    "system": "TSYS",
    "code": "51",
    "message": "Insufficient funds"
  }
}
```

You do not have to send `initial_network_transaction_id` when `initial_transaction_id` names a payment made through this gateway. The gateway looks the initial payment up itself. The response echoes `recurring_details` exactly as you sent it, though, so if you left `initial_network_transaction_id` out, it comes back `null` rather than filled in, even though the example above shows it. `network_transaction_id` is a new value for this payment.

## The four rules

The gateway enforces these and returns `400` with `INVALID_ACTION` when one is broken. **The schema does not describe them**, so a generated SDK will not catch them for you — the request looks valid right up until the API rejects it.

| Rule | Message when you break it |
|  --- | --- |
| `recurring_details` present means `initiator` is required | `initiator is required for recurring transactions` |
| `sequence: INITIAL` must use `initiator: CUSTOMER` | `initiator must be CUSTOMER for the initial recurring transaction` |
| `sequence: SUBSEQUENT` needs `initial_transaction_id` **or** `initial_network_transaction_id` | `Either initial_transaction_id or initial_network_transaction_id is required for subsequent recurring transactions` |
| `sequence: INITIAL` must omit both of those | `initial_transaction_id and initial_network_transaction_id must be omitted on the initial recurring transaction` |


The second rule is the one that trips people. An initial recurring payment **cannot** be merchant-initiated: the cardholder has to be present to consent to their card being stored. If you are setting up a series without the customer there, you do not have an initial payment — you have a subsequent payment on an agreement made somewhere else, and it needs rule three.

## Moving a series from another gateway

If the agreement started elsewhere, you have no `initial_transaction_id` to give, because the first payment was never a transaction here.

Send the network's identifier instead, in `recurring_details.initial_network_transaction_id`. That is what the field is for, and it is the only way to keep the stored-credential chain intact across a migration. Losing it usually shows up as a higher decline rate rather than an error.

## What this does not include

* **Installments are out of scope.** The gateway processes recurring payments. Fixed-instalment plans are not offered on the public API.
* **No schedule, no retries, no dunning.** Nothing here runs on a timer. Your system decides when to bill and what to do when a payment fails.
* **No customers, plans, or subscriptions.** Those resources do not exist. Your application owns them.


## Next steps

* [Save and reuse a payment method](/docs/payments/save-and-reuse-a-payment-method)
* [Handle declines](/docs/payments/handle-declines)
* [Find and report on transactions](/docs/after-the-payment/find-and-report-on-transactions)