### Verify one card by code, serial or username

`GET /api/v1/cards/search`

The counter-staff lookup. Finds a single card and reports its state, its owning reseller and who spent it. Confined to the caller's manager subtree unless they are an administrator.

- Authentication: manager session (JWT) or API token
- Permission: `prm_cards_verify` (Verify / redeem cards)
- Risk: write
- Rate limit bucket: `card_search`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `kind` | query | enum | yes | code, serial or username. |
| `keyword` | query | string | yes | The value to match. A code is normalised first, so separators and mixed case still resolve. |

#### Response — 200 OK

```json
{
  "data": {
    "card_id": 90211,
    "serial": "000312",
    "state": "consumed",
    "effective_state": "consumed",
    "disposition": "",
    "mode": "voucher_account",
    "plan_name": "Home 20M",
    "batch_id": 208,
    "face_value": 150.00,
    "redemptions_left": 0,
    "owner_manager_id": 41,
    "owner_username": "cairo-reseller",
    "used_by_kind": "user",
    "used_by_user_id": 4711,
    "used_by_username": "ahmed",
    "used_channel": "customer_portal",
    "first_use_at": "2026-09-14 18:22:10",
    "expires_at": "2027-01-01 00:00:00"
  }
}
```

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_NOT_FOUND` | 404 | no card matches, or the card is outside the caller's subtree |

#### Note

A card outside your subtree answers the same 404 as an unknown keyword, never a 403 — a 403 would confirm the code exists and turn this into a code-guessing oracle. It carries its own per-operator budget for the same reason, separate from the tenant's.

