# Requests and responses

> How every okdata call is shaped: paths, auth, the response envelope, paging with cursors, view and fields to trim output, and idempotent retries.

Canonical page: https://okdata.online/docs/requests/

## Paths and methods

Every endpoint lives at `https://api.okdata.online/v1/` followed by its id with dots as slashes. `douyin.videos.comments.list` is `GET https://api.okdata.online/v1/douyin/videos/comments/list`. Parameters go in the query string. An endpoint that takes a list (`ids`, `keywords`) is a POST with a JSON body instead; its page says so.

## Auth

Send the key as a bearer token:

```
Authorization: Bearer okd_...
```

The key never goes in the URL. The catalog, the status route and the MCP tools `find` and `describe` work without a key.

## The envelope

Every success has the same outer shape:

```json
{
  "code": "ok",
  "request_id": "0f6c2a8e-4b1d-4e0a-9a51-3c7d2b9e1f20",
  "data": { "items": [] },
  "page": { "next_cursor": "eyJvIjoyMH0", "has_more": true },
  "warnings": [],
  "meta": {
    "capability": "douyin.videos.search",
    "billing": { "amount": "0.004", "currency": "USD", "balance_after": "9.996" },
    "unavailable": { "metrics.views": "platform" },
    "cached": false,
    "replayed": false,
    "fetched_at": "2026-11-11T08:00:00Z"
  }
}
```

- List endpoints return `data.items` and a `page`. Single-object endpoints return `data.item` and no `page`.
- `meta.billing.amount` is the charge for this call, as a decimal string in US dollars. Do money maths on it as a decimal, never as a float.
- `meta.unavailable` maps each null field to a reason: `platform` (the platform never shows it) or `capability` (we cannot fetch it yet).
- `meta.cached` is true when the answer came from a short-lived cache; `meta.fetched_at` says when the data was read from the platform.

## Paging

One call returns one page. To get the next one, send `page.next_cursor` back as `cursor`, unchanged. `page.has_more: false` means you have everything. Each page is a separate call with its own charge, so stop as soon as you have enough:

```python
import os
import requests

URL = "https://api.okdata.online/v1/douyin/videos/comments/list"
HEADERS = {"Authorization": f"Bearer {os.environ['OKDATA_API_KEY']}"}

def comments(video_id, max_pages=5):
    cursor = None
    for _ in range(max_pages):
        params = {"id": video_id, **({"cursor": cursor} if cursor else {})}
        body = requests.get(URL, headers=HEADERS, params=params, timeout=60).json()
        if body["code"] != "ok":
            raise RuntimeError(body["message"])
        yield from body["data"]["items"]
        if not body["page"]["has_more"]:
            return
        cursor = body["page"]["next_cursor"]
```

Five pages of Douyin comments are five calls: $0.01.

## view and fields

Long responses cost tokens when a model reads them. Two parameters trim them:

- `view=compact` returns fewer fields per object, `standard` is the default, and `full` adds platform-specific extras under `ext`.
- `fields=id,text,metrics.likes` keeps only the fields you name, with dots for nested fields.

Neither changes the price.

## Retrying safely

Send an `Idempotency-Key` header with every call, a random string you keep with the request. Sending the same key with the same request again within 24 hours returns the stored result with `meta.replayed: true` and no new charge. The same key with a different request is rejected as `invalid_params`. On `rate_limited`, wait `retry_after_seconds`; on `not_found`, do not retry at all. The [errors page](/docs/errors/) lists every case.
