title: Authentication and API keys description: Bearer keys, two environments, per-key scopes, and what happens when a key leaks.
Every /v1 request authenticates with a bearer token.
curl -H "Authorization: Bearer pk_live_…" \
"https://markets.pulseline.io/v1/instruments?query=AAPL"
A missing or unparseable header returns
missing_api_key; an unknown key returns
invalid_api_key.
Two environments
Every account has both.
| Environment | Prefix | Data | Quota |
|---|---|---|---|
| Sandbox | pk_test_… | Real historical data | Its own separate allowance |
| Live | pk_live_… | Real data | Counts against your allowance |
Sandbox exists because the most-cited complaint about market-data APIs is hitting rate limits during development, before you have shipped anything. You should not pay to learn an API. Build and test against sandbox, then change one environment variable.
Sandbox requests still appear in your dashboard usage. They are recorded, just not billed.
Scopes
Keys carry explicit scopes, and each route requires exactly one:
| Scope | Grants |
|---|---|
| instruments:read | /v1/instruments, /v1/instruments/{id} |
| prices:read | /v1/instruments/{id}/prices, /v1/prices/bulk |
| actions:read | /v1/instruments/{id}/corporate-actions |
| earnings:read | /v1/instruments/{id}/earnings |
| fx:read | /v1/fx/{base}/{quote} |
There is deliberately no wildcard scope. A key that needs every route holds all five grants explicitly, because a wildcard is the one construct that turns a narrowly-issued key into a full-access key through a single mis-set field.
Matching is exact. A grant of prices does not imply prices:read. Calling a
route without its scope returns
insufficient_scope and does not consume
quota-bearing work beyond the rejected request itself.
Issue one key per consumer with only the scopes that consumer needs. That is what bounds the blast radius when a key leaks.
Creating and rolling keys
Keys are managed from your dashboard, never through the public API. An API key cannot mint another key or widen its own scopes, which is what stops a leaked key from escalating itself.
The full key is displayed exactly once, at creation. Only a hash and a short display prefix are stored, so we genuinely cannot show it to you again. Copy it into your secret store before you navigate away.
To roll a key without downtime:
- Create a new key with the same scopes.
- Deploy it to the consumer.
- Confirm traffic has moved, using the
lastUsedAtcolumn on the old key. - Revoke the old key.
Revocation is immediate. Authentication resolves every key against the database
on every request with no cache in front of it, so the next request made with a
revoked key is already rejected with
revoked_api_key. There is no TTL for a
withdrawn credential to outlive.
revoked_api_key is deliberately distinct from invalid_api_key so you can
tell "someone typo'd the key" from "this key was rolled and something is still
using the old one".
Handling keys safely
- Keep keys in a secret manager or environment variable. Never commit one.
- Never put a live key in a browser, a mobile binary, or anything you ship to a user. Call the API from your own backend.
- Use a separate key per environment and per service, so revoking one does not take down everything.
- If a key is exposed, revoke it first and investigate second. Issuing a replacement takes seconds.