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

# Authentication

> Environment-bound Blank API keys: format, scopes, creation, rotation, revocation, identity, and secret handling.

# Authentication

Blank API v2 authenticates with a single credential type: a **scoped, environment-bound, server-side API key**, sent as an HTTP bearer token.

```http theme={null}
Authorization: Bearer blank_live_...
```

Anonymous read operations need no credential at all. Everything else requires a key that carries the required scopes.

## What a key is bound to

A v2 API key belongs to exactly one:

* **Blank account** — the account that created it.
* **Environment** — `production`, `staging`, or `development`.
* **Wallet** — that account's single verified wallet, pinned at creation.

It also carries an explicit scope set, an expiry, a revocation state, and an auditable identifier. Ownership, scope, resource state, environment, rate policy, and any required wallet signature are re-checked on every protected request.

<Warning>
  An API key never replaces a wallet signature. It authorizes preparing and
  submitting a transaction; the pinned wallet still has to sign the exact
  prepared message. See [Operations and transaction
  intents](/docs/for-developers/operations).
</Warning>

Because a key is wallet-pinned, owner-scoped reads return only your own data. `blank.staking.positions()`, `blank.presales.participation()`, `blank.predictions.get()`, and `blank.predictions.eligibility()` can never see another wallet.

## Delegated prediction authorization

`predictions:delegate` is a narrow exception to account-pinned prediction submission. The API key still identifies and authenticates only the server-side integration. It can create a five-minute prediction intent, but it cannot create the end user's prediction until that user's verified wallet signs Blank's exact stored UTF-8 message.

The message binds the environment, integration key ID and name, intent ID, round ID, wallet, canonical predicted price, and expiry. Blank rejects a signature for a changed message, an intent created by another key, an expired or consumed intent, or a wallet that is not currently verified on an eligible Blank account.

This does not grant general impersonation. The scope cannot read the user's private resources or authorize trading, staking, fees, presales, or transactions. See [Delegated submissions](/docs/for-developers/predictions#delegated-submissions) for the server/browser/server flow.

## Key format and environments

| Environment   | Prefix        | Example shape                                   |
| ------------- | ------------- | ----------------------------------------------- |
| `production`  | `blank_live_` | `blank_live_<16-char locator>_<43-char secret>` |
| `staging`     | `blank_test_` | `blank_test_<16-char locator>_<43-char secret>` |
| `development` | `blank_test_` | `blank_test_<16-char locator>_<43-char secret>` |

Both segments are URL-safe base64. Blank stores only a SHA-256 hash of the credential, so a lost secret cannot be recovered — rotate instead.

A key is only accepted by the environment it was issued for. Presenting a `blank_test_` key to production returns `401 invalid_api_key`, and so does the reverse.

## Create a key

Keys are created in the Blank app, not through the API.

1. Sign in at [blank.build](https://blank.build) and open **Settings**.
2. Set a username first. Key creation is blocked until the account has one.
3. Enter a key name (1–80 characters).
4. Choose an expiry: **30, 90, 180, or 365 days**. Expiry is mandatory and can never exceed 365 days.
5. Select the scopes. At least one is required, and **scopes cannot be expanded after creation** — create a new key instead.
6. Copy the secret from the one-time reveal.

The environment is the environment of the Blank deployment you are signed in to; you cannot choose it.

<Note>
  Each account can hold at most **5 active keys per environment**. Revoke an
  unused key before creating another.
</Note>

## Inspect a key

```ts theme={null}
const identity = await blank.identity.me();
```

`GET /me` returns `ApiIdentity` and nothing else:

| Field           | Type                                       | Meaning                          |
| --------------- | ------------------------------------------ | -------------------------------- |
| `apiKeyId`      | uuid                                       | Stable identifier for audit logs |
| `userId`        | uuid                                       | Owning Blank account             |
| `walletAddress` | base58                                     | The wallet pinned to this key    |
| `name`          | string                                     | The key's display name           |
| `environment`   | `development` \| `staging` \| `production` | Environment binding              |
| `scopes`        | array                                      | Granted scopes                   |
| `expiresAt`     | RFC 3339                                   | When the key stops working       |

Use it as a startup self-check: fail fast on boot if `environment` or `scopes` are not what your service expects.

## Rotate a key

Rotate from **Settings** in the Blank app. Rotation issues a **new secret with a new key ID** and revokes the original; it does not extend an existing secret. You may set a new expiry at rotation time, still bounded to 365 days.

Because rotation produces a new credential rather than overlapping secrets, the safe sequence is:

1. Create the rotated key and copy the new secret.
2. Write it to your secret manager.
3. Roll your deployment so every process reads the new value.
4. Confirm traffic on the new key ID via `GET /me` and your own logs.

Reading the credential through a function makes step 3 a config change rather than a redeploy:

```ts theme={null}
const blank = new BlankClient({
  apiKey: async () => await secrets.get("BLANK_API_KEY"),
});
```

Webhook signing secrets rotate differently — they support an explicit overlap window. See [Webhooks](/docs/for-developers/webhooks).

## Revoke a key

Revoke from **Settings**. Revocation takes effect for subsequent requests: the key immediately fails authentication with `401 invalid_api_key`. Key creation, rotation, and revocation are all recorded as `api_key.*` audit events on the account.

Revoke immediately if a secret is exposed in a log, a screenshot, a repository, a CI artifact, or a support ticket.

## Handling the secret

* Store it in a secret manager or environment variable. Never in source control, never in a client bundle, never in a container image layer.
* Never log the credential. Log `apiKeyId` and `X-Request-Id` instead.
* Give each service and each environment its own key so revocation is surgical.
* Grant the narrowest scope set that works. A read-only integration should never hold `predictions:write` or `webhooks:write`.
* Treat a one-time reveal as the only copy. There is no recovery path.

<Warning>
  Never construct a `BlankClient` with an `apiKey` in browser or mobile code.
  The SDK throws in a browser runtime, and the API sends no
  `Access-Control-Allow-Origin` for authenticated operations, so the request
  would fail in the browser anyway. If a browser needs private data, proxy it
  through your own server.
</Warning>

## Anonymous access

These operations require no key and are browser-reachable: token list and detail, market snapshot, candles, trades, holder stats, staking pool and leaderboard, presale list and detail, prediction rounds, round entries, standings, and the Solana manifest.

They are rate-limited per IP and globally, and they prove nothing about who is calling. Never treat an anonymous read as authentication.

## Authentication failures

| Status | Code                   | Cause                                                                                         |
| ------ | ---------------------- | --------------------------------------------------------------------------------------------- |
| 401    | `invalid_api_key`      | Missing, malformed, unknown, expired, revoked, wrong-environment key, or an unverified wallet |
| 403    | `insufficient_scope`   | The key is valid but does not carry every scope the operation requires                        |
| 503    | `identity_unavailable` | `GET /me` could not read identity state; retryable                                            |

`invalid_api_key` is deliberately undifferentiated so the API does not confirm whether a credential exists. Check the prefix, the environment, and the expiry, then rotate if all three look right.

## Next

<Columns cols={2}>
  <Card title="Scopes" icon="shield" href="/docs/for-developers/scopes">
    The full scope catalogue and the operations each one unlocks.
  </Card>

  <Card title="Conventions" icon="settings" href="/docs/for-developers/conventions">
    Rate limits, request IDs, retries, deadlines, and idempotency.
  </Card>
</Columns>
