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

# Client Keys

> Public, scoped API keys for embedding in SDKs and browser code. Narrow-scope alternative to full server keys.

Orchestra issues two kinds of API keys. Server keys stay secret. Client keys are public.

|                                              | Server key             | Client key                               |
| -------------------------------------------- | ---------------------- | ---------------------------------------- |
| Prefix                                       | `fn_`                  | `fnp_`                                   |
| Secrecy                                      | Secret, hashed at rest | Public, visible anytime in the dashboard |
| Capabilities                                 | Everything             | Quote, submit, onramp, address creation  |
| Dashboard access                             | Yes                    | No                                       |
| Webhooks, affiliates, transaction history    | Yes                    | No                                       |
| Embedding in open-source SDKs / browser code | No                     | Yes                                      |

Use client keys when the key will ship inside something your users can inspect — a mobile app, a web bundle, an open-source SDK. Use server keys for anything running on a backend you control.

## When to pick which

If you own the environment the key runs in (your servers, a VPC, a private container), use a server key. If end users can extract the key by opening devtools, decompiling an APK, or reading a git repo, use a client key.

## Getting a key

Every new partner is provisioned with both keys automatically. Find them in the dashboard under **API Keys**. Client keys can be revealed at any time (they're public). Server keys are shown once at creation and hashed afterward.

Additional keys of either type can be created from the same page.

## Scopes

Client keys are scope-gated. Only these actions are permitted:

| Scope                 | Endpoint                              |
| --------------------- | ------------------------------------- |
| `orders:quote`        | `POST /v1/orchestration/quote`        |
| `orders:submit`       | `POST /v1/orchestration/submit`       |
| `orders:onramp`       | `POST /v1/orchestration/onramp`       |
| `orders:read`         | `GET /v1/orchestration/status?id=...` |
| `orders:sse`          | `GET /v1/sse/operations/:id`          |
| `accumulation:create` | `POST /v1/accumulation-addresses`     |
| `liquidation:create`  | `POST /v1/liquidation-addresses`      |

Any request outside this allowlist returns `403 forbidden` with `"This route is not available for client keys"`. There is no opt-in: client keys cannot read transaction history, manage webhooks, or touch affiliate data, regardless of what scopes are assigned.

## Modes

When you create a client key you choose a mode. The mode determines how the `Origin` header is enforced.

| Mode      | Origin check                                                             |
| --------- | ------------------------------------------------------------------------ |
| `server`  | Not checked. Use from backends that don't send `Origin`.                 |
| `browser` | `Origin` header required, must match the key's allowed origins.          |
| `both`    | If `Origin` is present it must match; if absent, the request is allowed. |

Allowed origins are a per-key list configured at creation time. Leave the list empty to accept any origin (useful when you can't enumerate SDK consumers up front). Global CORS (`CORS_ALLOWED_ORIGINS`) still applies in every case.

## Read-tokens

Client keys are shared across many end users. If user A calls `submit`, user B must not be able to read user A's order by guessing the ID. Orchestra enforces this with short-lived HMAC read-tokens.

```
# Submit under a client key — response contains a readToken
{
  "orderId": "op_...",
  "readToken": "eyJ...ABC"
}

# Read the order using the token — pass via X-Read-Token header OR ?readToken=
GET /v1/orchestration/status?id=op_... HTTP/1.1
Authorization: Bearer fnp_...
X-Read-Token: eyJ...ABC
```

The token is bound to `(partnerId, apiKeyId, orderId, expiry)`. Tokens for other orders, other keys, or expired tokens return `403 read_token_required` or `403 invalid_read_token`.

Server keys don't need a read-token — they can read any order under the partner.

SSE subscriptions (`/v1/sse/operations/:id`) follow the same rule. Because `EventSource` can't set headers, pass the read-token as a query param:

```
GET /v1/sse/operations/op_...?token=fnp_...&readToken=eyJ... HTTP/1.1
```

## Rate limits

Client keys are rate-limited at two layers. Server keys are not limited by these buckets.

| Route                 | Per-key/min | Per-(key, IP)/min |
| --------------------- | ----------- | ----------------- |
| `orders:quote`        | 600         | 60                |
| `orders:submit`       | 120         | 10                |
| `orders:onramp`       | 120         | 10                |
| `accumulation:create` | 60          | 5                 |
| `liquidation:create`  | 60          | 5                 |

The per-(key, IP) layer stops a single abusive user from exhausting the partner's aggregate budget. The per-key layer is the DoS ceiling. Both return `429 rate_limited` with `Retry-After` when tripped.

## Privileged fields are stripped

Request bodies passed with a client key have privileged fields silently removed before validation. This includes anything that would let a caller redirect fees, override tiers, or inject admin flags. The request continues as if those fields were never sent; no error surfaces so the schema stays opaque.

## Revocation and disable

Client keys support two lifecycle actions:

* **Disable** (`POST /v1/partner/dashboard/api-keys/:id/disable`) — the key starts failing auth immediately, but in-flight operations continue to completion. Reversible: an operator can re-enable the key via the dashboard or by clearing `disabled_at` in the database.
* **Revoke** (`DELETE /v1/partner/dashboard/api-keys/:id`) — permanent. Sets `revoked_at`; in-flight operations continue, but no new requests succeed.

Disable first when rotating; revoke only when a key will never be used again.

## End-to-end example

```bash theme={null}
# 1. Quote
curl -sS -X POST "https://orchestration.flashnet.xyz/v1/orchestration/quote" \
  -H "Authorization: Bearer fnp_..." \
  -H "X-Idempotency-Key: quote:$(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceChain": "base",
    "sourceAsset": "USDC",
    "destinationChain": "spark",
    "destinationAsset": "BTC",
    "amount": "100000000",
    "recipientAddress": "spark1...",
    "slippageBps": 50
  }'

# 2. Submit — response includes readToken
curl -sS -X POST "https://orchestration.flashnet.xyz/v1/orchestration/submit" \
  -H "Authorization: Bearer fnp_..." \
  -H "X-Idempotency-Key: submit:$(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "quoteId": "quote_...", "depositTransactionId": "0x..." }'
# -> { "orderId": "op_...", "readToken": "eyJ..." }

# 3. Poll status — read-token required (pass as ?readToken= or X-Read-Token header)
curl -sS "https://orchestration.flashnet.xyz/v1/orchestration/status?id=op_..." \
  -H "Authorization: Bearer fnp_..." \
  -H "X-Read-Token: eyJ..."
```

The rest of the Orchestra flow (deposit handling, webhook delivery, repricing) is identical to the server-key flow documented in [Quickstart](/products/orchestration/integration).
