# Response format

Every response, from every platform, success or failure, has the same four top-level keys. That is the whole point of Onekey: you write your parsing once.

```json
{
  "success": true,
  "data": { "author": { "username": "torvalds" } },
  "metadata": {
    "platform": "github",
    "endpoint": "profile",
    "cached": false,
    "stale": false,
    "age_s": 0,
    "fetched_at": "2026-01-01T12:00:00+00:00",
    "credits_charged": 1,
    "request_id": "7f3a9c2e5b1d4c8fa2e6d0b9c4f18e37"
  },
  "error": null
}
```

## Fields

| Field | Type | Meaning |
| --- | --- | --- |
| `success` | boolean | `true` if you got data. Check this first. |
| `data` | object, array or null | The unified result. An object for lookups, an array for searches and lists. `null` on failure. |
| `metadata.platform`, `metadata.endpoint` | string | Which endpoint answered. |
| `metadata.cached` | boolean | `true` if this came from Onekey's cache instead of a fresh fetch. |
| `metadata.stale` | boolean | `true` if the cached copy is past its fresh window (see [Caching](https://onekey.dev/docs/caching.md)). |
| `metadata.age_s` | number | Seconds since the data was fetched. `0` for a fresh fetch. |
| `metadata.fetched_at` | string | When the data was fetched, ISO 8601. |
| `metadata.credits_charged` | number | What this call cost, for example `1`, or `0` for a cache hit. |
| `metadata.request_id` | string | Quote this if you contact support. Also in the `x-request-id` header. |
| `metadata.idempotent_replay` | boolean | `true` when this body was replayed for an [`Idempotency-Key`](https://onekey.dev/docs/idempotency.md) you sent before (and `credits_charged` is `0`). |
| `error` | object or null | `null` on success. On failure: `type`, `code`, `message`, `request_id`, `doc_url` (see [Errors](https://onekey.dev/docs/errors.md)). |
| `pagination` | object | Only on list endpoints that page: `next_cursor`, `has_more`, `page_size` (see [Pagination](https://onekey.dev/docs/pagination.md)). Absent everywhere else. |
| `resolved` | object | Only on `GET /v1/lookup`: the `platform`, `endpoint` and `params` the URL resolved to (see [URL lookup](https://onekey.dev/docs/lookup.md)). |

> [!NOTE] Responses may gain extra informational fields over time. Read the fields you need and ignore the rest.

## Compound calls

[Batch](https://onekey.dev/docs/batch.md), [multi-platform search and creator card](https://onekey.dev/docs/search-multi.md) run several endpoint calls in one request. They keep the four top-level keys, but `data` holds one row per call (a list for a batch, an object keyed by platform for the recipes), each row carrying its own `status`, `data`, `error`, `credits_charged` and `cached`; the endpoint-level `metadata` fields (`platform`, `cached`, `age_s`...) move into the rows. Their `metadata` has instead:

| Field | Meaning |
| --- | --- |
| `legs` | How many calls ran. |
| `succeeded`, `failed` | How many rows have `status: 200`, and how many do not. A `200` with `failed` above 0 is a partial success. |
| `credits_charged` | The sum of what the rows cost. |
| `recipe` | `search_multi` or `creator_card`, with the input (`query` or `handle`) and `platforms`. Absent on a batch. |
| `fuse` | `{"method": "rrf", "k": 60}` when `fuse=rrf` was asked for. |

## Inside data

Endpoints that declare a [computed block](https://onekey.dev/docs/computed.md) add `data.computed` (an object of derived values such as `engagement_rate` and `language`, `null` when unknown) to every item, and `data._warnings` (a list of strings) only when there is something to say about those values.

## A failure looks like this

```json
{
  "success": false,
  "data": null,
  "metadata": { "request_id": "1b2c3d4e6f7a48b9c0d1e2f3a4b5c6d7" },
  "error": {
    "code": "not_found",
    "type": "NOT_FOUND",
    "message": "Not found",
    "request_id": "1b2c3d4e6f7a48b9c0d1e2f3a4b5c6d7",
    "doc_url": "https://onekey.dev/errors/NOT_FOUND"
  }
}
```

Error messages are short and generic on purpose. Branch on `error.type` (or `error.code`, the same word in lower case), which is stable, not on the message text.

## Unified fields

Different platforms put the same idea in different places. Onekey maps them to shared names, so a follower count is always `author.followers`:

| Group | Common fields |
| --- | --- |
| `author` | `id`, `username`, `displayName`, `followers`, `following`, `url`, `avatarUrl`, `verified` |
| `content` | `id`, `title`, `text`, `url`, `createdAt`, `language` |
| `engagement` | `likes`, `comments`, `shares`, `views`, `bookmarks` |
| `media` | `thumbnailUrl`, `url`, `durationSeconds` |
| `entity` | `id`, `name`, `description`, `url`, `stats` (shared counters such as `stars`, `downloads`, `rating`) |
| `market`, `place`, `listing`, `package`, `paper`, `event` | Typed groups for tickers and quotes, locations, products and jobs, repositories and packages (`package.version`), scholarly works and events |
| `ext` | Platform-specific extras, flat camelCase names, listed on each endpoint page |

Fields a platform does not have are `null`, never missing, so you can read them without checking. The full list is on the [unified schema](https://onekey.dev/docs/schema.md) page. Each [endpoint page](https://onekey.dev/docs/platforms.md) shows a recorded response.
