Skip to main content
How to authenticate with the QWED API.

API keys

All requests require an API key, except GET /health and GET /health/identity, which are unauthenticated by design — qwed init calls the identity endpoint before it has a key to send.

Header authentication

SDK authentication

Environment variables

Set your API key as an environment variable:
Then the SDK will auto-detect it:

API key format

QWED API keys use a fixed prefix followed by a fixed-length body. The prefix tells you the key type. The body shape tells you the key version.

Production key versions

QWED issues new keys in the v2 format. Existing v1 keys keep working and are never force-rotated. qwed_test_ keys use the same body versions; a test key can be a structurally valid v2 (checksummed) key, so treat the prefix—not the shape—as the production/non-production signal. The v2 checksum is CRC32 (IEEE, as in zlib) over the ASCII encoding of the first 30 body characters. QWED Base62-encodes the result with the alphabet 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz and zero-pads it to a width of 6. You can validate a v2 key offline: split the body into the first 30 and last 6 characters, recompute the checksum, and accept only on an exact match. This reduces random false-positive matches to about 2^-32 without a database call.
The checksum authenticates the key’s shape, not its authorization. A key with a valid checksum still has to pass the server-side HMAC lookup before QWED accepts it.

Scanning for leaked keys

If you build a secret scanner, leak detector, or pre-commit hook, use these rules:
  • Match only qwed_live_. Exclude qwed_test_ from high-confidence production-secret matching. Registered scanner patterns match only keys with the qwed_live_ prefix, so scanners do not report test keys to QWED. The revocation sink itself applies no prefix filter.
  • Anchor with lookarounds, not \b. A v1 body can end in -, which no word boundary borders. Use charset-complement lookarounds so a match ends exactly where the body alphabet stops.
  • Post-process v2 matches. Recompute the checksum and discard matches that fail. For v1, enforce the exact shape only.
  • With the RE2 patterns below, use the capture group. Send group 1 as the token and resume scanning after it; the whole match includes surrounding delimiter characters.
On regex engines without lookaround support (for example, RE2), use an explicit delimiter class instead. The key is wrapped in a capture group; the surrounding (?:^|[^...]) and (?:[^...]|$) delimiters match characters that are not part of the key, so always read the match’s group 1 as the token, never the whole match. Submitting the whole match sends one or two extra characters around the key, which breaks checksum validation and the revocation lookup. Group 1 also lets a global scan continue from the end of the key rather than the end of the whole match, so back-to-back keys separated by a single delimiter are not skipped:
The registered pattern names are QWED_LIVE_API_KEY (v2) and QWED_LIVE_API_KEY_V1 (v1). QWED receives signed leak reports for these patterns at POST /webhooks/secret-scanning.

Masking keys

When you display a key in logs, dashboards, documentation, or examples, show the prefix, asterisks, and only the last 4 characters. Never show a full key body.

Machine-readable spec and test vectors

Scanner and SDK authors can consume the canonical format directly:
  • spec/api-key-format.json: prefixes, full and anchored patterns, checksum parameters, and excluded prefixes.
  • spec/api-key-test-vectors.json: synthetic, non-functional valid and invalid keys with expected classifications (v2, v1, invalid, or excluded for test-prefix keys that are shape-valid but must never alert). None of these values authenticate.
  • docs/API_KEY_FORMAT.md: the human-readable specification.
Run your validator against every vector before shipping.

API key storage and migration (v7.2)

As of v7.2, QWED stores API keys as HMAC-SHA256 lookup digests keyed by a dedicated server-side secret (QWED_API_KEY_LOOKUP_SECRET). Earlier versions used PBKDF2, which added ~67ms of CPU cost to every request on the unauthenticated lookup path. Because API keys are high-entropy random tokens, the key-derivation cost bought no additional brute-force resistance, so it was removed.
Breaking change. There is no legacy fallback: API keys issued before v7.2 no longer authenticate and return 401 Unauthorized. Each key must be re-issued once. The old raw key is never required:
  • Portal or API: sign in with your email and password to get a JWT, then call POST /auth/api-keys to mint a new key. This path needs no API key.
  • Rotation by key ID: call POST /admin/keys/rotate with the key’s ID, authenticated with an already-working key in the same organization. Members can rotate only their own keys. Owners and admins can also rotate other users’ keys, unless the key owner holds a higher role. Revoked or inactive keys can’t be rotated.

Self-hosted configuration

Self-hosted deployments must set QWED_API_KEY_LOOKUP_SECRET before starting the server:
  • The server fails closed at startup if the variable is missing.
  • The value must differ from QWED_JWT_SECRET_KEY. Equal values are rejected at startup, because reusing the JWT secret would break every API-key lookup on the next JWT-secret rotation.
  • Set the secret before issuing any v7.2 keys. Digests are derived from the secret active at issue time, so changing it later requires a one-time re-issue of all keys.

Security best practices

  1. Never commit API keys to version control
  2. Use environment variables in production
  3. Rotate keys regularly using the dashboard
  4. Set IP allowlists for production keys
  5. Use test keys for development

Agent authentication

Agent tokens are separate from API keys and are used for agent-specific endpoints.
Agent tokens are passed via the X-Agent-Token header:

Scopes

API keys can have restricted scopes:

Auth endpoints

The following endpoints manage user accounts and API keys via JWT-based authentication. Anonymous /auth/* routes are rate limited per client IP (default 10 requests per minute). Exceeding the limit returns 429 Too Many Requests with a Retry-After header. See Per-IP limits on authentication endpoints.

POST /auth/signup

Create a new user and organization. Returns a JWT token for immediate use. Request:
Response:

POST /auth/signin

Sign in an existing user. Request:
Response: Same format as /auth/signup.

GET /auth/me

Get the current authenticated user’s information. Requires a Bearer token in the Authorization header.

POST /auth/api-keys

Generate a new API key for the current user’s organization. Requires JWT authentication. Request:
Response:
The key field is only returned once at creation time. Store it securely.

GET /auth/api-keys

List all active API keys for the current user’s organization.

DELETE /auth/api-keys/

Revoke an API key. Performs a soft delete.

Audit endpoints

These endpoints require JWT authentication (Bearer token) and return data scoped to the authenticated user’s organization.

GET /audit/logs

Get audit logs for the current organization.

GET /audit/logs/

Get detailed information for a single audit log entry.

GET /audit/export

Export audit logs as a CSV file (up to 1,000 records).