# Catalogue

`GET /v1/catalog` returns the offers this partner can sell in the requested currency. The
catalogue is partner-specific: discounts, currency conversion and eligibility are applied before
the response.

```http
GET /v1/catalog?currency=USD
```

| Query | Values | Default |
| --- | --- | --- |
| `currency` | `USD`, `EUR`, `GBP` | `USD` |

Unknown currency values fall back to USD.

## Eligibility

An offer is returned only when all of these are true:

- It covers exactly one country.
- The listing is active in a live store.
- The upstream product is approved for sale.
- The wholesale cost is positive.
- The current retail price is at or above the contribution-margin floor.
- The product is deduplicated by its upstream product identifier.

The current selector enforces the single-country rule. Regional and global offers are part of the
target catalogue contract but are **Planned**; do not depend on them yet.

## Response

```json
{
  "currency": "USD",
  "offers": [
    {
      "offerId": "11111111-1111-4111-8111-111111111111",
      "name": "Thailand 15 GB 7 days",
      "network": "ExampleNet",
      "validityDays": 7,
      "destination": "TH",
      "dataGbPerDay": null,
      "dataGbTotal": 15,
      "isUnlimited": false,
      "available": true,
      "prices": {
        "USD": 899,
        "EUR": 829,
        "GBP": 719
      },
      "discount": {
        "requestedPercent": 10,
        "appliedPercent": 10,
        "amount": 90,
        "finalAmount": 809,
        "capped": false
      }
    }
  ]
}
```

| Field | Meaning |
| --- | --- |
| `offerId` | Opaque, stable identifier. Use it in quotes and one-call orders. |
| `name` | Sanitised public title. Never contains an upstream store or product code. |
| `network` | Public network label. |
| `validityDays` | Validity window in days. |
| `destination` | ISO 3166-1 alpha-2 country code. |
| `dataGbPerDay`, `dataGbTotal`, `isUnlimited` | Allowance description. |
| `available` | Advisory stock flag. Not a reservation. |
| `prices` | List price in minor units for all three currencies. |
| `discount` | Partner discount, applied discount, amount, final unit price and cap flag. |

Amounts are integer minor units: cents for USD and EUR, pence for GBP.

## Pricing and FX

The catalogue is priced in USD and converted using the latest FX snapshot. If the EUR or GBP rate
is missing or stale, that currency returns:

```json
{
  "error": {
    "code": "fx_unavailable",
    "message": "The selected currency is temporarily unavailable.",
    "requestId": "..."
  }
}
```

HTTP status is `503`. Retry later or request USD. The quote pins the FX snapshot used for its
pricing, so the order recheck uses the same contract as the quote.

## Availability and stock

`available` is advisory. A catalogue read does not reserve stock. The quote and the order both
recheck price and stock; the order is the first operation that can fail with `409 out_of_stock`
or `409 price_changed`.

## Discounts and caps

Each partner has a discount percentage. `requestedPercent` is the partner's entitlement;
`appliedPercent` is what the margin floor allows. When the requested discount would breach the
floor, `capped` is true and `finalAmount` is raised to the floor. Always use
`finalAmount`, not the arithmetic of `requestedPercent`.

## What is not exposed

The response deliberately omits upstream store, product code, cost, margin floor, stock count and
upstream identity. Public names are sanitised. If an offer cannot be sold white-label, it is not
in the catalogue. Do not attempt to infer or reconstruct those fields.

## Planned catalogue controls

The target contract adds filters and cursor pagination. These parameters are **Planned** and their
names may change:

| Parameter | Purpose |
| --- | --- |
| `country` | ISO country code or destination filter. |
| `type` | Local, regional or global. |
| `validityDays` | Exact or ranged validity filter. |
| `search` | Name, destination or network search. |
| `cursor` | Opaque cursor from a previous page. |
| `limit` | Page size. |

Until they ship, request the currency and page through the returned `offers` array. Treat the
response as the full eligible set for that currency, not as a promise that every upstream product
is present.

## Related

- [Quotes](/docs/quotes) to bind an offer and quantity.
- [Orders](/docs/orders) to buy the quote or use one-call ordering.
- [Errors](/docs/errors) for `out_of_stock`, `price_changed` and `fx_unavailable`.
