### List the cards in a series

`GET /api/v1/card-batches/{id}/cards`

One row per card, carrying the plaintext code and, for prepaid series, the login username and password. Behind both the card-list permission and the per-series subtree gate for that reason.

- Authentication: manager session (JWT) or API token
- Permission: `prm_cards_list` (View card batches & cards)
- Risk: read

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Series id. |
| `filter[state]` | query | string | no | Stored state — unused, consumed, active, exhausted, revoked. |
| `filter[disposition]` | query | string | no | Who holds the card, which is orthogonal to state. |
| `filter[has_customer]` | query | boolean | no | Cards linked to a subscriber. |

#### Response — 200 OK

```json
{
  "data": [
    {
      "id": 90211,
      "serial": "000312",
      "code": "K7P4M2Q9XD",
      "username": "v90211",
      "password": "8sk3ndq1",
      "state": "unused",
      "effective_state": "unused",
      "disposition": "available",
      "redemptions_left": 1,
      "expires_at": "2027-01-01 00:00:00",
      "used_bytes": 0,
      "used_seconds": 0
    }
  ],
  "meta": { "page": 1, "page_size": 50, "total": 500, "has_next": true }
}
```

#### Note

state and effective_state are different questions and you almost always want the second. Nothing ever writes state 'expired', so a voucher past its expiry still reads as 'unused' in state and shows as available stock; effective_state applies the expiry at read time. Meanwhile disposition answers who holds the card — a voucher sold at the till but not yet redeemed is still 'unused' and is not stock you can sell again.

