# Testing your integration

There is no separate sandbox host: tests run against the real API with a real key, so the aim is to test as much as possible without spending credits, and to cap what the rest can spend.

## Use a separate capped key

Create a key for tests in the [dashboard](https://onekey.dev/dashboard/keys) and give it a [credit limit](https://onekey.dev/docs/key-limits.md). When the limit is reached calls fail with [`KEY_BUDGET_EXCEEDED`](https://onekey.dev/docs/errors.md#key-budget-exceeded) and your balance is untouched, so a loop in a test cannot drain the account.

## Start with free calls

These cost nothing, so a test suite can call them freely (still within the rate limit):

| Call | What it proves |
| --- | --- |
| `GET /v1/account` | The key works and what the balance is. |
| `GET /v1/platforms` | The catalog and each endpoint's price, parameters and status. |
| `GET /v1/status` | Per-platform health. Public, no key. See [Status](https://onekey.dev/docs/status.md). |
| `GET /v1/errors` | Every error type your client may need to handle. |
| A request with a bad parameter | A free `422 INVALID_PARAMS` or `400` (never charged), so you can test your error path. |
| A repeat of a call within its cache window | A free cache hit: `X-Cache: HIT`, `credits_charged: 0`. |

For multi-leg calls, `dry_run=true` on [universal search](https://onekey.dev/docs/universal-search.md#dry-run) and the [reports](https://onekey.dev/docs/reports.md#dry-run) returns the plan and the worst-case price for 0 credits.

## Record a fixture and test your parser

Every endpoint page has a recorded response in the real envelope. Save one as a fixture and run your parser against it. Then add cases for the awkward shapes:

- A field that is `null` (unknown). Do not coerce it to zero.
- A field the endpoint does not map at all (absent). See [Data availability](https://onekey.dev/docs/data-availability.md).
- An empty list: `data: []` is a normal success.
- An extra field your code has never seen. It must be ignored.
- `metadata.stale: true` and `metadata.cached: true`.

## Test the failure paths

Send these on purpose and assert what your code does:

| Send | Expect |
| --- | --- |
| No `x-api-key` or a wrong one | `401 UNAUTHORIZED`. Not retried. |
| A missing required parameter | `422`. Not retried. |
| A record that does not exist | `404 NOT_FOUND`. Not charged, not retried. |
| A burst over the limit | `429` with `Retry-After`. Your client waits and retries. |

## Test retries with idempotency

Send the same request twice with one `Idempotency-Key`. The second answer has `metadata.idempotent_replay: true`, `credits_charged: 0` and the header `X-Idempotent-Replay: true`. Send the same key with different parameters and you get [`IDEMPOTENCY_KEY_REUSED`](https://onekey.dev/docs/errors.md#idempotency-key-reused). See [Idempotency](https://onekey.dev/docs/idempotency.md).

## Test webhook delivery

For [monitors](https://onekey.dev/docs/monitors.md), call `POST /v1/monitors/{id}/webhook/test` to send a signed `test` event to your endpoint without waiting for a schedule. Assert that your handler:

1. Reads the raw body bytes before parsing.
2. Verifies `Onekey-Signature` in constant time and rejects a timestamp more than 300 seconds old.
3. Answers `2xx` within 5 seconds.
4. Ignores a second delivery with the same `Onekey-Delivery-Id`.

The [webhook verifier tool](https://onekey.dev/tools/webhook-verifier) checks a captured body and signature in your browser.

## One live smoke test

Keep one cheap real call in CI, on a stable endpoint, to catch a bad key or a changed contract:

```bash
curl "https://api.onekey.dev/v1/github/profile?username=torvalds" \
  -H "x-api-key: $ONEKEY_API_KEY"
```

## Clean up

Delete test monitors when a run finishes (`DELETE /v1/monitors/{id}`), since a monitor keeps running and billing on its schedule until paused or deleted. See [Monitors](https://onekey.dev/docs/monitors.md#when-a-monitor-pauses).

## Next

- [Production checklist](https://onekey.dev/docs/production-checklist.md).
- [Errors](https://onekey.dev/docs/errors.md).
