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

# Browsing the catalog

> Every filter and sort option on /offers, what an offer contains, and how to read availability.

`GET /offers` is the endpoint you will spend most of your requests on. It returns
what is rentable right now, filtered however you like.

```bash theme={null}
curl -s -G https://api.gpuoutlet.ai/v1/offers \
  -H "Authorization: Bearer $GPUOUTLET_API_KEY" \
  -d gpu_family=h100-80gb \
  -d region=us-east-1 \
  -d available=true \
  -d sort=price_asc
```

Requires `catalog:read`. Cursor-paginated — see [Pagination](/api/pagination),
and note in particular that **a short page is not the last page**.

## Filters

All are optional and combine with AND.

### By GPU

<ParamField query="gpu_model" type="string">
  Exact SKU code, e.g. `h100-sxm-80gb`. The narrowest filter — one specific card
  in one specific form factor. Values come from [`/gpu-models`](/api/reference-data).
</ParamField>

<ParamField query="gpu_family" type="string">
  Family id, e.g. `h100-80gb`. Broader than `gpu_model`: covers every variant of
  a card, so `h100-80gb` matches both SXM and PCIe. **This is usually the filter
  you want** — most workloads care about "an H100", not about the interconnect.
  Values come from [`/gpu-families`](/api/reference-data).
</ParamField>

<ParamField query="min_vram_gb" type="integer">
  Minimum VRAM per GPU, in gibibytes. Filter by what your model needs
  (`min_vram_gb=48`) rather than by name, and you will match cards you had not
  thought to ask for.
</ParamField>

<ParamField query="q" type="string">
  Free-text over GPU name and code. For a human-typed search box. Prefer the
  structured filters in code — `q=h100` also matches things you did not mean.
</ParamField>

### By location

<ParamField query="region" type="string">
  Exact region code, e.g. `us-east-1`. From [`/regions`](/api/reference-data).
</ParamField>

<ParamField query="geo" type="string">
  Coarse bucket: `us`, `eu`, `asia`, `other`. Use it when the requirement is
  "keep the data on this continent" rather than a specific datacentre.
</ParamField>

### By price and shape

<ParamField query="max_price_per_hour_cents" type="integer">
  Ceiling on the hourly price, in cents, **per GPU**. `max_price_per_hour_cents=300`
  is "no more than \$3.00/hr".
</ParamField>

<ParamField query="instance_class" type="'standard' | 'dedicated'">
  `standard` — the shared-host pool, cheapest and most plentiful.
  `dedicated` — the whole host is yours, for workloads that cannot tolerate a
  neighbour.
</ParamField>

<ParamField query="service_tier" type="'on_demand' | 'spot'">
  Only offers rentable under this tier. `spot` is cheaper and interruptible —
  see [Spot rentals](/api/rental-billing#spot-and-preemption).
</ParamField>

### By availability

<ParamField query="available" type="'true' | 'false'">
  `true` returns only offers with capacity right now.

  This is a **snapshot, not a reservation**. An offer can be taken between your
  read and your `POST /rentals`, which is why creation can still answer
  `offer_unavailable`. Handle that; do not assume `available=true` guarantees a
  machine.
</ParamField>

## Sorting

<ParamField query="sort" type="string" default="price_asc">
  `price_asc` · `price_desc` · `vram_asc` · `vram_desc` · `availability_desc`
</ParamField>

`availability_desc` puts the deepest capacity first — the right sort when you
need eight GPUs and would rather not discover the shortage at creation time.

## Paging

<ParamField query="cursor" type="string">
  Opaque; from `meta.next_cursor`. Valid only for the exact query that produced it.
</ParamField>

<ParamField query="limit" type="integer" default="50">
  1–200.
</ParamField>

## What an offer looks like

```json theme={null}
{
  "id": "off_3kd91mz7",
  "gpu_model": "h100-sxm-80gb",
  "gpu_name": "NVIDIA H100 SXM",
  "gpu_family": "h100-80gb",
  "gpu_arch": "Hopper",
  "gpu_vendor": "NVIDIA",
  "vram_gb": 80,
  "ram_gb": 192,
  "vcpu": 24,
  "max_gpus": 8,
  "instance_class": "standard",
  "region": "us-east-1",
  "region_name": "US East",
  "geo": "us",
  "currency": "usd",
  "price_per_hour_cents": 249,
  "spot_price_per_hour_cents": 137,
  "availability": { "kind": "count", "count": 6 },
  "service_tiers": ["on_demand", "spot"]
}
```

<ResponseField name="id" type="string" required>
  Opaque, and stable for as long as the offer exists — but offers are transient.
  Do not store one and expect it to be rentable tomorrow; re-read the catalog.
</ResponseField>

<ResponseField name="price_per_hour_cents" type="integer" required>
  **Per GPU**, per hour, in cents. A two-GPU rental of this offer costs twice
  this. (Note that the *quote* reports the price for the whole rental — the two
  are different numbers on purpose, and the quote is the one to show a user.)
</ResponseField>

<ResponseField name="spot_price_per_hour_cents" type="integer | null">
  The interruptible price, when this offer sells one. `null` means it does not.
</ResponseField>

<ResponseField name="max_gpus" type="integer | null">
  The largest `gpu_count` this offer can serve. `null` when the upstream does not
  say.
</ResponseField>

<ResponseField name="gpu_family" type="string | null">
  Join key to `/gpu-families` and `/prices`. `null` for models outside a known
  family.
</ResponseField>

<ResponseField name="service_tiers" type="string[]" required>
  Which tiers this offer can be rented under **right now**. This array is the
  authority on what you can buy; the price fields only say how much.

  It cuts both ways. An offer with a `spot_price_per_hour_cents` but no `spot`
  here cannot currently be taken as spot. An offer with no `on_demand` here
  cannot be taken on-demand either, even though `price_per_hour_cents` is still
  populated — some machines are sold at their spot rate only, and the on-demand
  figure is then the reference that rate undercuts rather than an offer.

  Send the tier you want on `POST /rentals`. Omitting it means on-demand, so an
  integration that never sets it will be refused on a spot-only offer rather
  than quietly charged a different rate.
</ResponseField>

## Reading `availability`

This field is a discriminated union. Check `kind` before reading anything else:

<CodeGroup>
  ```json Exact count theme={null}
  { "kind": "count", "count": 6 }
  ```

  ```json Coarse signal theme={null}
  { "kind": "signal", "level": "high" }
  ```
</CodeGroup>

```python theme={null}
av = offer["availability"]
if av["kind"] == "count":
    free = av["count"]              # exact
else:
    free = {"high": 5, "medium": 2, "low": 1}[av["level"]]   # your own heuristic
```

Some upstreams report an exact number of free units; others only report a rough
level. Rather than invent a number for the second case — which would be a
fabricated integer indistinguishable from a real one — we tell you which kind of
answer you are holding.

<Warning>
  `{"kind": "count", "count": 0}` and `available=false` mean the same thing. Do not
  write `if offer["availability"]["count"]:` without handling the `signal` case —
  a `KeyError` on a perfectly valid response is the most common first-integration
  bug on this endpoint.
</Warning>

## One offer by id

```bash theme={null}
curl -s https://api.gpuoutlet.ai/v1/offers/off_3kd91mz7 \
  -H "Authorization: Bearer $GPUOUTLET_API_KEY"
```

Returns the offer object directly, not wrapped in `data`. `404 offer_not_found`
covers both "no such offer" and "no longer visible" — reported identically on
purpose.

## Worked examples

<AccordionGroup>
  <Accordion title="Cheapest H100 available right now, anywhere" icon="dollar-sign">
    ```
    /offers?gpu_family=h100-80gb&available=true&sort=price_asc&limit=1
    ```
  </Accordion>

  <Accordion title="Anything with at least 48 GB, in Europe, under $2/hr" icon="euro-sign">
    ```
    /offers?min_vram_gb=48&geo=eu&max_price_per_hour_cents=200&available=true
    ```

    Filtering by VRAM rather than by model is what makes this useful: it will surface
    an L40S or an A6000 you would not have thought to name.
  </Accordion>

  <Accordion title="Room for an 8-GPU job" icon="layer-group">
    ```
    /offers?gpu_family=h100-80gb&available=true&sort=availability_desc
    ```

    Then check `max_gpus >= 8` and, where `availability.kind == "count"`,
    `count >= 8`. Neither field alone is sufficient: `max_gpus` is what the offer
    *can* serve, `count` is what is free.
  </Accordion>

  <Accordion title="Cheap interruptible capacity for a checkpointed job" icon="bolt">
    ```
    /offers?service_tier=spot&available=true&sort=price_asc
    ```

    Only for work that can be killed and resumed — see
    [Spot and preemption](/api/rental-billing#spot-and-preemption).
  </Accordion>

  <Accordion title="A whole host to yourself" icon="server">
    ```
    /offers?instance_class=dedicated&gpu_family=h100-80gb&available=true
    ```
  </Accordion>
</AccordionGroup>
