> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hyparrow.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscriptions

> Create recurring billing agreements, collect renewals with virtual accounts, and read payment history.

## Overview

A **subscription** is a recurring billing agreement that links a customer to one of your products
at a fixed interval. Hyparrow tracks the billing period, the next billing date, and a complete
payment history for each subscription.

A subscription always belongs to a customer and a product, and bills in one of these intervals:

| Interval       | Bills every |
| -------------- | ----------- |
| `daily`        | 1 day       |
| `every_3_days` | 3 days      |
| `weekly`       | 7 days      |
| `biweekly`     | 14 days     |
| `monthly`      | 1 month     |
| `quarterly`    | 3 months    |
| `biannual`     | 6 months    |
| `yearly`       | 1 year      |

<Note>
  **Base URL:** `https://api.hyparrow.cloud/api/v1`

  Authenticate every request with both headers:

  ```
  X-API-Key: pk_live_xxxxxxxxxxxx
  X-API-Secret: sk_live_xxxxxxxxxxxx
  ```

  Use `https://sandbox.hyparrow.cloud/api/v1` with `pk_test_` keys to dry-run a full billing
  cycle — generate a virtual account, simulate a transfer, and confirm the status moves to
  `active` without moving real money.
</Note>

<Note>
  Subscription amounts in API responses are in **naira** (e.g. `4500.00`), derived from the
  product price. Where you reconcile against transactions or webhook deliveries elsewhere in the
  Hyparrow API, money is denominated in **kobo** (1 naira = 100 kobo).
</Note>

## Lifecycle

A subscription moves through a small set of states:

<Steps>
  <Step title="pending_payment">
    Created but not yet paid. No service period has started. This is the initial status of every
    new subscription.
  </Step>

  <Step title="active">
    The first payment has been collected. `currentPeriodStart`, `currentPeriodEnd`, and
    `nextBillingAt` are set, and the subscription will bill again at the interval you chose.
  </Step>

  <Step title="canceled">
    Cancelled by you via the cancel endpoint. Billing stops; no further renewals are attempted.
  </Step>

  <Step title="expired">
    A renewal was missed and the grace window (`graceDays`, default 3) elapsed without payment.
  </Step>
</Steps>

## Create a subscription

`POST /subscriptions/`

| Field                 | Required | Notes                                               |
| --------------------- | -------- | --------------------------------------------------- |
| `customerId`          | yes      | UUID of an existing customer                        |
| `productId`           | yes      | UUID of the product to bill                         |
| `interval`            | yes      | One of the billing intervals above                  |
| `paymentMethod`       | no       | `card`, `manual`, or `va` (defaults to `va`)        |
| `graceDays`           | no       | Days after a missed payment before expiry           |
| `tax_direction`       | no       | `merchant` (default) or `customer`                  |
| `checkoutCallbackUrl` | no       | URL notified when a checkout payment changes status |
| `metadata`            | no       | Arbitrary key/value object                          |

```bash theme={null}
curl -X POST https://api.hyparrow.cloud/api/v1/subscriptions/ \
  -H "X-API-Key: pk_live_xxxxxxxxxxxx" \
  -H "X-API-Secret: sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "c1a2b3c4-d5e6-7a8b-9c0d-1e2f3a4b5c6d",
    "productId": "8f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
    "interval": "monthly",
    "graceDays": 3,
    "tax_direction": "merchant"
  }'
```

```json theme={null}
{
  "success": true,
  "message": "Subscription created",
  "data": {
    "id": "5d6e7f80-1a2b-3c4d-5e6f-7a8b9c0d1e2f",
    "customerId": "c1a2b3c4-...",
    "productId": "8f1c2d3e-...",
    "interval": "monthly",
    "amount": 4500.00,
    "currency": "NGN",
    "paymentMethod": "va",
    "status": "pending_payment",
    "graceDays": 3,
    "createdAt": "2026-06-30T10:20:00Z"
  }
}
```

The subscription starts in `pending_payment`. To activate it, collect the first payment — usually
by generating a virtual account (below).

## Generate a virtual account to collect payment

A **virtual account (VA)** is a temporary, dedicated bank account number. Your customer transfers
the exact subscription amount to it; once the transfer lands, Hyparrow records the payment and
moves the subscription to `active`.

`POST /subscriptions/{subscriptionId}/generate-va`

`expiryMinutes` must be one of `10`, `15`, or `30`.

<Steps>
  <Step title="Generate the account">
    Call generate-va. You receive an account number, bank name, and an expiry timestamp.
  </Step>

  <Step title="Show it to the customer">
    Display the account details and the exact amount. Transfers must arrive before `expiresAt`.
  </Step>

  <Step title="Wait for confirmation">
    When the transfer settles, the subscription becomes `active` and a payment is recorded. If you
    have a webhook configured, a `subscription.payment.completed` event is delivered.
  </Step>
</Steps>

```bash theme={null}
curl -X POST https://api.hyparrow.cloud/api/v1/subscriptions/5d6e7f80-.../generate-va \
  -H "X-API-Key: pk_live_xxxxxxxxxxxx" \
  -H "X-API-Secret: sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "expiryMinutes": 30 }'
```

```json theme={null}
{
  "success": true,
  "message": "Virtual account generated — pay the exact amount before it expires",
  "data": {
    "id": "9a0b1c2d-...",
    "subscriptionId": "5d6e7f80-...",
    "accountNumber": "7012345678",
    "accountName": "HYPARROW / ACME STORE",
    "bankName": "Sterling Bank",
    "bankCode": "232",
    "expiresAt": "2026-06-30T10:50:00Z",
    "isActive": true
  }
}
```

<Warning>
  Customers must transfer the **exact** amount. The account expires at `expiresAt`; after that you
  must generate a fresh one.
</Warning>

### Fetch the active virtual account

`GET /subscriptions/{subscriptionId}/va` returns the current active VA (useful for re-displaying
details), or `404` if none is active.

```bash theme={null}
curl https://api.hyparrow.cloud/api/v1/subscriptions/5d6e7f80-.../va \
  -H "X-API-Key: pk_live_xxxxxxxxxxxx" \
  -H "X-API-Secret: sk_live_xxxxxxxxxxxx"
```

## Mark a subscription as paid

For payments collected outside the VA flow (for example a confirmed manual bank transfer), record
the payment directly with `POST /subscriptions/{subscriptionId}/pay`. This advances the billing
period and activates the subscription.

```bash theme={null}
curl -X POST https://api.hyparrow.cloud/api/v1/subscriptions/5d6e7f80-.../pay \
  -H "X-API-Key: pk_live_xxxxxxxxxxxx" \
  -H "X-API-Secret: sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "reference": "MANUAL-2026-0042", "payerName": "Ada Obi" }'
```

```json theme={null}
{
  "success": true,
  "message": "Subscription marked as paid",
  "data": {
    "id": "p1a2b3c4-...",
    "subscriptionId": "5d6e7f80-...",
    "amount": 4500.00,
    "currency": "NGN",
    "reference": "MANUAL-2026-0042",
    "payerName": "Ada Obi",
    "paymentSource": "manual",
    "periodStart": "2026-06-30T10:55:00Z",
    "periodEnd": "2026-07-30T10:55:00Z",
    "paidAt": "2026-06-30T10:55:00Z"
  }
}
```

## Cancel a subscription

`POST /subscriptions/{subscriptionId}/cancel` stops all future billing.

```bash theme={null}
curl -X POST https://api.hyparrow.cloud/api/v1/subscriptions/5d6e7f80-.../cancel \
  -H "X-API-Key: pk_live_xxxxxxxxxxxx" \
  -H "X-API-Secret: sk_live_xxxxxxxxxxxx"
```

```json theme={null}
{
  "success": true,
  "message": "Subscription canceled",
  "data": { "id": "5d6e7f80-...", "status": "canceled", "canceledAt": "2026-06-30T11:00:00Z" }
}
```

## Get and list subscriptions

<CodeGroup>
  ```bash Get one theme={null}
  curl https://api.hyparrow.cloud/api/v1/subscriptions/5d6e7f80-... \
    -H "X-API-Key: pk_live_xxxxxxxxxxxx" \
    -H "X-API-Secret: sk_live_xxxxxxxxxxxx"
  ```

  ```bash List theme={null}
  curl "https://api.hyparrow.cloud/api/v1/subscriptions/?status=active&page=1&limit=20" \
    -H "X-API-Key: pk_live_xxxxxxxxxxxx" \
    -H "X-API-Secret: sk_live_xxxxxxxxxxxx"
  ```
</CodeGroup>

The list endpoint supports `page`, `limit`, and a `status` filter, and returns pagination metadata:

```json theme={null}
{
  "success": true,
  "data": [ { "id": "5d6e7f80-...", "interval": "monthly", "status": "active", "amount": 4500.00 } ],
  "pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 }
}
```

## Payment history

`GET /subscriptions/{subscriptionId}/payments` lists every recorded payment for a subscription,
paginated. Each entry shows the amount, reference, payer, payment source, and the billing period
it covers.

```bash theme={null}
curl "https://api.hyparrow.cloud/api/v1/subscriptions/5d6e7f80-.../payments?page=1&limit=20" \
  -H "X-API-Key: pk_live_xxxxxxxxxxxx" \
  -H "X-API-Secret: sk_live_xxxxxxxxxxxx"
```

```json theme={null}
{
  "success": true,
  "data": [
    {
      "id": "p1a2b3c4-...",
      "subscriptionId": "5d6e7f80-...",
      "amount": 4500.00,
      "currency": "NGN",
      "reference": "MANUAL-2026-0042",
      "payerName": "Ada Obi",
      "paymentSource": "manual",
      "periodStart": "2026-06-30T10:55:00Z",
      "periodEnd": "2026-07-30T10:55:00Z",
      "paidAt": "2026-06-30T10:55:00Z"
    }
  ],
  "pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 }
}
```

<Note>
  To be notified of renewals automatically rather than polling this endpoint, configure a webhook
  URL and listen for the `subscription.payment.completed` event. See the
  [Webhooks](/webhooks) guide.
</Note>
