Authentication
One header. Server-side keys, class-tagged live vs test, scoped least-privilege — and no wallet signatures anywhere.
Drazill authentication is deliberately plain: an integration sends one API key in one header. There is no request-signing ceremony to get started, no wallet to connect, no EIP-712 payloads. (An optional per-key HMAC signature is available for high-assurance bots — see Request signing below — but it is opt-in, not the default.)
The X-API-Key header
API keys are server-side secrets. The API’s CORS policy allows only Drazill’s own
first-party web origins to send credentials, so a third-party browser app cannot call the
API directly with a key — browser and mobile apps must proxy requests through a backend
you control that holds the key. (This is enforced, not advisory: cross-origin credentialed
requests from other origins are rejected. Source: docs/api/cors-and-auth.md.)
Bearer tokens are Drazill’s own session scheme
The API also accepts Authorization: Bearer <jwt> — but those are first-party session
tokens minted by the Drazill web/mobile apps for a signed-in user, not a credential you
provision for an integration. Build integrations with API keys. Bearer auth is
documented here only so you recognize it in the reference.
Key classes: live vs test
Every key is class-tagged by its prefix, set at creation (environment: "live" | "test"):
Never paste a drzl_live_ key into a browser, a notebook you share, or any client you do
not control. Full details in Paper vs live and Sandbox.
Source: app/schemas/api_key.py, app/models/api_key.py.
Scopes
Keys carry least-privilege scopes. A new key defaults to read-only scopes; write scopes
must be requested explicitly and acknowledged (acknowledge_write_scopes: true), and
they require a verified email on the owning account (ensure_write_scopes_email_verified,
app/api/routes/api_keys.py). Admin scopes are never grantable to a user-created key —
a leaked or self-minted key can never perform admin actions
(validate_user_creatable_scopes, app/schemas/api_key.py).
The env:paper scope marks a key as sandbox/paper-only; websocket is required to
authenticate the realtime feed. This table is generated from the scope constants in
app/schemas/api_key.py and drift-gated in CI — it cannot fall out of sync with the code.
Key lifecycle
- Create —
POST /api/v1/api-keyswith aname,environment, optionalscopes, and optionalexpires_in_days(1–365; omit for no expiry). The full key is returned once and never again — store it immediately. - Rotate — issue a new secret for a key while keeping its identity. Rotation dual-signs webhooks during a grace window so you can roll secrets without missing deliveries (see Webhooks).
- Revoke — disable a key immediately.
You receive a security email on every create / rotate / revoke — showing the key
prefix only, never the raw secret (docs/runbooks/developer-platform.md).
Request signing
A key created with signing_required: true must HMAC-sign every request. The signing
secret (prefix drzl_sign_) is returned once at creation and is never sent over the
wire. Sign the string "<unix_ts>.<METHOD>.<path>." + body with the secret and attach
two headers:
X-Drazill-Timestamp: <unix seconds>X-Drazill-Signature: v1=<hex hmac-sha256>
Requests outside a ±300-second skew window are rejected. This is a leak-resistant second
factor for bots: even a stolen key is useless without the separate signing secret. Source:
app/services/api_signing.py.
Withdrawals require an MFA step-up
Initiating a withdrawal requires a fresh MFA step-up: call POST /api/v1/security/mfa/step-up,
whose proof is cached for 300 seconds (MFA_STEP_UP_TTL_SECONDS). Within that window the
withdrawal proceeds; after it, the step-up must be repeated. Source: app/api/routes/security.py,
app/core/config.py.
Developer terms
PENDING LEGAL REVIEW. The developer API terms (docs/legal/api-terms.md,
version 2026-07-17) are a good-faith engineering draft that has not been reviewed
or approved by legal counsel and is not a binding agreement. It is mirrored here with
that status, exactly as the source carries it.
Terms acceptance is gated by DEVELOPER_TERMS_REQUIRED, which is false today — key
creation does not currently require recorded acceptance. When the owner enables it, creating
a key will require a current acceptance of API_TERMS_VERSION (2026-07-17), re-requested
whenever the version changes. This page states the real flag state rather than implying a
gate that is off. Source: app/core/config.py, docs/legal/api-terms.md.
Authenticating the WebSocket
The realtime feed authenticates via the first message on the socket (not a header or query param), accepting either credential:
Source: app/api/routes/websocket.py. The full realtime auth flow is under
Realtime.

