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

# Concepts

> How every data type behaves, so you only have to learn it once

Everything on this page is true of every data type.

## The four endpoints

<AccordionGroup>
  <Accordion title="/latest — what is true now">
    Current values, one row per thing. This is the endpoint for "what is the price today".
  </Accordion>

  <Accordion title="/as-of?at=… — what was true then">
    The same shape as `/latest`, rewound to a date you choose. `at` takes a date or a full
    timestamp; a plain date means the end of that day, so `at=2026-06-01` gives you the position at
    the close of 1 June.

    If nothing was published on that exact date — a weekend, a holiday, a gap in a vendor's feed —
    you get the most recent value **before** it, rather than an empty response.
  </Accordion>

  <Accordion title="/history?start=…&end=… — what changed">
    Everything that changed between two dates. See [How history works](#how-history-works) below,
    which is the one behaviour worth reading properly if you are coming from v1.

    If you leave `start` out, you get a sensible recent window for that data type — 30 days for
    rental prices and indices, and 90 days for everything else. Each `/history` endpoint states
    its own default.
  </Accordion>

  <Accordion title="/metadata — what can I ask for">
    The values that data type accepts as filters, what it covers, and how far back you can read
    (`history_starts_at`). It is quick, and it is the right first call for any type — it tells you
    what a query will accept before you write one.
  </Accordion>
</AccordionGroup>

**Aggregated Transactions has only `/history` and `/metadata`.** A settled deal is an event that
happened on a date, so there is no "current value" to ask for. The other two endpoints return `404`
on that type.

## What a response looks like

Every response that returns rows has the same two parts:

```json theme={null}
{
  "data": [ /* your rows */ ],
  "meta": {
    "page_size": 100,
    "returned": 100,
    "has_more": true,
    "next": { "url": "…", "path": "…", "query_params": { } },
    "sort": { "by": "valid_from", "order": "desc" }
  }
}
```

`data` is what you asked for. `meta` describes the request it answers — the sort that was applied,
the date range that was used, and whether there is more to fetch. If you sent no sort parameters,
`meta.sort` tells you which order you got rather than leaving you to guess.

Fields with no value come back as `null` rather than being left out, so a key you expect is always
there. `null` means *not published* — which is not the same as zero.

## Paging

Responses come back a page at a time. To get the next page, **call what the response gives you**.

`meta.next` gives you that call in **two** forms. Both describe the same request, so pick whichever
fits your HTTP client:

| Option                  | Fields to use                           | Use it when                                                              |
| ----------------------- | --------------------------------------- | ------------------------------------------------------------------------ |
| **Fully-qualified URL** | `url`                                   | you can call an absolute URL as given                                    |
| **Path + parameters**   | `path` **together with** `query_params` | you keep your own base URL, or your client takes parameters as a mapping |

<Warning>
  **`path` is not a third option on its own.** The `page_token` lives in `query_params`, and the
  token is what carries your filters, window, ordering *and* your position in the walk. Call `path`
  by itself and you get the first page back again — unfiltered, and on every iteration, so a paging
  loop never terminates. On a data type that requires an anchor filter you get a `400` instead.

  Always send `path` and `query_params` together.
</Warning>

Mid-walk, `meta.next` looks like this:

```json theme={null}
"next": {
  "url": "https://data-api.compute-index.com/v2/gpu/rental/prices/latest?page_token=eyJ…&page_size=100",
  "path": "/v2/gpu/rental/prices/latest",
  "query_params": { "page_token": "eyJ…", "page_size": "100" }
}
```

Both loops below walk the same result set:

<CodeGroup>
  ```python Following url theme={null}
  url = "https://data-api.compute-index.com/v2/gpu/rental/prices/latest?gpu_search=H100"
  rows = []
  while url:
      body = requests.get(url, headers=headers, timeout=30).json()
      rows += body["data"]
      nxt = body["meta"]["next"]
      url = nxt["url"] if nxt else None
  ```

  ```python Following path + query_params theme={null}
  # `path` already carries the /v2 mount, so pair it with the bare origin.
  ORIGIN = "https://data-api.compute-index.com"

  path, params = "/v2/gpu/rental/prices/latest", {"gpu_search": "H100"}
  rows = []
  while path:
      body = requests.get(f"{ORIGIN}{path}", params=params, headers=headers, timeout=30).json()
      rows += body["data"]
      nxt = body["meta"]["next"]
      # Take BOTH, and REPLACE rather than merge: `path` alone has no token, and
      # anything left over from the first request is rejected beside one.
      path, params = (nxt["path"], nxt["query_params"]) if nxt else (None, None)
  ```
</CodeGroup>

When `meta.next` is `null` you have everything. `meta.has_more` says the same thing as a boolean.

<Warning>
  **Send nothing alongside the next link** except `page_size`. It already carries your filters,
  dates and sort order, so adding a parameter returns an error rather than being quietly ignored.
  To change a filter, start again without it.
</Warning>

`page_size` defaults to 100 and goes up to 1000. There is no page number and no total count —
`has_more` answers the question a paging loop actually asks.

## Date ranges

`start` is included and `end` is not. Consecutive ranges therefore join up without overlapping, so
you can pull a year of data month by month and never see the same row twice.

Plain dates behave as you would expect: `?start=2026-06-01&end=2026-06-01` is that whole day.

All times are UTC.

<h2 id="how-history-works">
  How history works
</h2>

`/history` returns records that **changed** inside your date range.

<Warning>
  This is the one thing that behaves differently from v1. v1's `/history` returned everything still
  in force during the window, so a price set months ago and never changed came back every time. In
  v2 it does not — you get the changes.
</Warning>

Which endpoint you want depends on the question:

* **Charting a series over time, or syncing changes since you last looked?** `/history`.
* **What was the price on a particular day?** `/as-of` — that is exactly what it answers.

## Consistent names

The same thing is spelled the same way on every data type, so once you have learned a name you know
it everywhere. Where a concept genuinely differs, so does the name — hardware listings take
`country`, not `region`, because a country is not a cloud region.

Each type sorts on its own set of fields, always with a sensible default, and the order applied is
always reported back in `meta.sort`. Filters that take a list accept up to 20 values.
