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

# Start without a key

> One curl returns real settled picks with no account. The sandbox answers every other operation with its documented example. Then the 3 steps to live data.

Two things answer with no account, no key, and no card:

1. The **[Pick of the Day ledger](/api-reference/endpoint/get-pick-of-the-day-ledger)** on `api.0xinsider.com`, which is production data.
2. The **sandbox** at `https://0xinsider.com/sandbox`, which answers every documented operation with its example payload.

Start with the first, use the second to shape your code, then swap 2 things for live data.

## 1 request, real data

```bash theme={null}
curl "https://api.0xinsider.com/api/v1/pick-of-the-day/ledger" \
  | jq '.data.entries[] | select(.state == "opened")' | tail -30
```

One settled entry, as the route returned it:

```json theme={null}
{
  "state": "opened",
  "pick_date": "2026-09-20",
  "pick_rank": 4,
  "commitment_hash": "65948da47edfb88418a3b3d76f050fb9b8bbf625f3752f41cff9e542a568f36c",
  "commitment_nonce": "f1677e8dd365699c1cb6cfbfc0bb5a1ad6a51f60108643ce2f5af96f70e831e6",
  "commitment_algo": "sha256(canonical_json(payload)||nonce)",
  "sealed_at": "2026-09-20T19:25:12.404010Z",
  "kickoff": "2026-09-20T20:25:00Z",
  "payload": {
    "backed_price": "0.525000",
    "condition_id": "0xe8c438da07835b401331142e8db93e4f12b4a96c33bf53f1dda269e2b2a324e5",
    "kickoff": "2026-09-20T20:25:00Z",
    "pick_date": "2026-09-20",
    "pick_outcome_index": 0,
    "pick_outcome_label": "Over",
    "pick_rank": 4,
    "platform": "polymarket"
  },
  "outcome": "win",
  "matchup": "Commanders vs. Cowboys",
  "category": "Football",
  "permalink": "https://0xinsider.com/pick-of-the-day/2026-09-20/4"
}
```

The hash was published 1 hour before kickoff, and the nonce and the payload came after the game settled. SHA-256 over the payload bytes plus the decoded nonce reproduces `commitment_hash`, so you can check the pick did not move.

A live pick is `sealed`: it carries the hash, `sealed_at`, and `kickoff`, and nothing that states a side or a price.

The route takes no parameters and returns the whole ledger, one entry per `(pick_date, pick_rank)`, with `entry_count`, `sealed_count`, `opened_count`, and `uncommitted_count` alongside `entries`. Any query parameter you add is ignored and named in the `X-Query-Ignored` response header. [`0xinsider/picks`](https://github.com/0xinsider/picks) mirrors it into a public git history and ships a `verify.py`.

That is the whole onboarding for read-and-verify work. Nothing below is needed for it.

## Every other operation: the sandbox

```bash theme={null}
curl "https://0xinsider.com/sandbox/api/v1/leaderboard?limit=5"
```

The sandbox is the second `servers` entry of the [OpenAPI document](https://0xinsider.com/api/v1/openapi.json). Append any documented path to `https://0xinsider.com/sandbox` and you get that operation's documented example: the same envelope, the same field names, the same types, the same cursor keys. Every response carries `X-Oxi-Sandbox: true`.

| You send                                                | The sandbox answers                                                                                   |
| ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| Any documented JSON `GET`                               | `200` with that operation's body, filled from a fixed seven-row world                                 |
| `?limit=` and `?cursor=` on a list                      | That page of the seven rows; the last page reads `has_more: false` and `next_cursor: null`            |
| A batch write                                           | One item per identity you posted, in order, with the meta counts matching                             |
| Any other documented JSON write, webhooks included      | The documented success body. Nothing is stored.                                                       |
| A parameter or body outside the document                | The `400` or `415` the live API returns, with the same `error.code`, `error.reason` and `error.param` |
| `?sandbox_status=<code>`                                | That error, for any status the operation documents                                                    |
| A Markdown route, the SSE stream, or an export download | `400 bad_request`, naming the route. Call the live API for those.                                     |
| An undocumented path                                    | `404 not_found`                                                                                       |
| An undocumented method                                  | `405` with `Allow`                                                                                    |

Every value is one the live API could return: a grade is a grade letter, `platform` is `polymarket`, an address is 40 hex characters, and a trader's `id` is `trd_` plus that address. None of it is real. The wallets all start `0x51ab` and no Polymarket wallet does, the markets are named "Sandbox Rovers" and "Sandbox Open", and the 7 rows never change.

The sandbox proves your parsing, your error branches, your pagination, and your request shapes. It proves nothing about grades, prices, or flow.

## Page through a list

```bash theme={null}
curl "https://0xinsider.com/sandbox/api/v1/leaderboard?limit=3"
```

Every cursor-paginated list serves the same seven rows.

```json theme={null}
{ "has_more": true, "next_cursor": "sbx_3", "total": 7 }
```

Follow `next_cursor` until it is `null`: `sbx_3` gives rows 4 to 6, `sbx_6` gives the last row with `has_more: false` and `next_cursor: null`. Write the loop here and it terminates. A cursor the sandbox did not issue, or one past the end, is `400 bad_request` with `error.reason` `cursor_expired`, which is what the live API returns for a cursor that no longer resolves.

## It refuses what the live API refuses

```bash theme={null}
curl -i "https://0xinsider.com/sandbox/api/v1/leaderboard?limit=0"
```

```json theme={null}
{ "code": "bad_request", "message": "Query parameter 'limit' must be at least 1.", "param": "limit", "reason": "invalid_query" }
```

Documented query parameters and JSON request bodies are checked against the same OpenAPI document the sandbox answers from:

* a value outside its schema is `400` with `error.reason` `invalid_query`;
* a body that is missing, is not JSON, or does not fit the request schema is `400 invalid_body`, with `error.param` naming the field;
* a body without `Content-Type: application/json` is `415 unsupported_media_type`;
* an unknown query name is ignored and reported in `X-Query-Ignored`, and `X-Query-Validation: strict` makes it `400 unknown_query_parameter`. Successful reads also carry `X-Effective-Query`.

So a client that sends a shape the live API rejects finds out here, before it has a key.

## Simulate an error before you meet one

```bash theme={null}
curl -i "https://0xinsider.com/sandbox/api/v1/leaderboard?sandbox_status=429"
```

```
HTTP/2 429
retry-after: 60

{"object":"error","error":{"code":"rate_limited","message":"Sandbox simulated a 429: the rate limit was exceeded.","retry_at":"2026-09-22T18:45:55.940Z"},"meta":{"request_id":"req_sandbox","cached":false,"cost":1}}
```

Any status the operation documents works: `400`, `401`, `402`, `403`, `404`, `423`, `429`, `500`, or `503`. A `429` or `503` carries `Retry-After: 60` and `error.retry_at`.

Write the branch for each one here, where a mistake costs nothing. [Errors](/errors) lists the codes.

## The sandbox key is optional

```bash theme={null}
curl -X POST "https://api.0xinsider.com/api/v1/agents/register"
```

A `201` returns an `oxi_sk_test_` key, the sandbox base URL, and every URL on the path to live access. Use it when your client or your agent framework insists on a credential. The sandbox answers the same with or without it, nothing is stored, and the key cannot be listed or revoked.

The live API refuses it with `401 invalid_api_key` and `error.reason` `sandbox_api_key`. That is deliberate: a sandbox key can never touch production data by accident. [Register an agent](/api-reference/endpoint/register-agent) has the fields.

## Move to live data

Live data is a paid product. 3 steps, and only the first has a wait:

<Steps>
  <Step title="Subscribe to Pro">
    [Pricing](https://0xinsider.com/pricing). Without it every data route answers `402 subscription_required`.
  </Step>

  <Step title="Generate the key">
    [Developers](https://0xinsider.com/developers), then **Generate token**. The full key shows once.
  </Step>

  <Step title="Change 2 things in your code">
    The base URL becomes `https://api.0xinsider.com`, and every request carries `Authorization: Bearer $OXINSIDER_API_KEY`. Field names, envelopes, and cursors do not change.
  </Step>
</Steps>

Every official client makes step 3 one line:

| Client     | Sandbox                                                                 | Live                                                   |
| ---------- | ----------------------------------------------------------------------- | ------------------------------------------------------ |
| Python     | `oxinsider.Client.sandbox()`                                            | `oxinsider.Client()`, which reads `OXINSIDER_API_KEY`  |
| Go         | `oxinsider.New(oxinsider.WithBaseURL("https://0xinsider.com/sandbox"))` | `oxinsider.New(oxinsider.WithBearerToken(key))`        |
| TypeScript | `OxinsiderApiClient.sandbox()`                                          | `new OxinsiderApiClient({ apiKey })`                   |
| `curl`     | `https://0xinsider.com/sandbox/api/v1/...`                              | `https://api.0xinsider.com/api/v1/...` plus the header |

## What this does not give you

* A free tier on production data. The 6 [public routes](/authentication#what-needs-a-key), the Remote MCP handshake, and the sandbox are everything that works without a key. Everything else needs Pro.
* Real numbers from the sandbox. A `pnl` of `1284500.42` belongs to a wallet that does not exist.
* A stream, a Markdown context document, or an export file from the sandbox. Those answer `400` there.
* A sandbox key that works on the live API, or a live key that changes a sandbox answer.

## Go next

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/quickstart">
    The same 5 reads, keyless first, then live.
  </Card>

  <Card title="Python client" icon="python" href="/integrations/python-client">
    `pip install 0xinsider`, sandbox in 3 lines.
  </Card>

  <Card title="Go client" icon="golang" href="/integrations/go-client">
    `go get github.com/0xinsider/0xinsider-go`.
  </Card>

  <Card title="TypeScript client" icon="js" href="/integrations/typescript-client">
    `OxinsiderApiClient.sandbox()`, with no key.
  </Card>

  <Card title="Rust client" icon="rust" href="/integrations/rust-client">
    `Client::sandbox()`, with no key.
  </Card>

  <Card title="Authentication" icon="key" href="/authentication">
    The 6 public routes, the 2 credentials, and where a key belongs.
  </Card>
</CardGroup>
