Authentication
Every request to the Qpher API must be authenticated. This page explains how authentication works, what happens when it fails, and how to keep your credentials secure.
API Key Authentication
The primary authentication method for the Qpher API is an API key passed in
the X-API-Key header.
curl -X POST https://api.qpher.ai/api/v1/kem/encrypt \
-H "X-API-Key: qph_your_key_here" \
-H "Content-Type: application/json" \
-d '{"plaintext": "SGVsbG8gV29ybGQh", "key_version": 1}'The header is X-API-Key (case-insensitive). Do not use
Authorization: Bearer for API key authentication -- that pattern
is reserved for JWT-based portal sessions.
The Auth Pipeline
When your request reaches the Qpher API Gateway, it passes through a 7-stage authentication and authorization pipeline before reaching the target service. Understanding this pipeline helps you diagnose errors and optimize performance.
Pipeline Stages
| Stage | What Happens | On Failure |
|---|---|---|
| 1. Skip Check | Determines if the endpoint is public (/health, /metrics, /docs). Public endpoints bypass all remaining stages. | -- |
| 2. Extract | Reads the X-API-Key header from the request. | 401 ERR_AUTH_001 |
| 3. Resolve | Hashes the API key and looks up the associated tenant. Verifies the key is valid and active. | 401 ERR_AUTH_001 |
| 4. Inject | Adds X-Tenant-ID, X-Request-ID, and X-API-Key-Version headers to the request for downstream services. | -- |
| 5. Rate Limit | Checks the tenant's request rate against their plan's limit using a Redis-backed sliding window. | 429 ERR_RATE_LIMIT_001 |
| 6. Policy | Evaluates the request against the tenant's access policy (plan restrictions, endpoint permissions, quota limits). | 403 ERR_FORBIDDEN_001 |
| 7. Route | Forwards the authenticated, authorized request to the target microservice. | 502 or 504 |
If the Policy Engine is unavailable, the gateway denies all requests (HTTP 503). Qpher never falls back to permissive mode. This fail-closed behavior ensures that a service outage cannot be exploited to bypass authorization.
Auth Error Codes
When authentication or authorization fails, the API returns a structured error response. Here are the error codes you may encounter:
| HTTP Status | Error Code | Description | Common Cause |
|---|---|---|---|
| 401 | ERR_AUTH_001 | Missing or invalid API key | No X-API-Key header, or the key is revoked/expired |
| 403 | ERR_FORBIDDEN_001 | Operation not allowed | Your plan does not permit this operation, or a policy rule denied the request |
| 403 | ERR_POLICY_001 | Policy engine denied request | Tenant-specific policy restriction |
| 429 | ERR_RATE_LIMIT_001 | Rate limit exceeded | Too many requests in the current time window |
| 503 | ERR_SERVICE_001 | Service unavailable (fail-closed) | The Policy Engine or a critical service is temporarily down |
Error Response Format
All errors follow a consistent structure:
{
"error": {
"code": "ERR_AUTH_001",
"message": "Missing or invalid API key",
"details": "Provide a valid API key in the X-API-Key header"
},
"request_id": "d4e5f6a7-b8c9-0123-def0-123456789abc",
"timestamp": "2026-01-15T10:30:00Z"
}
The request_id is always present, even on error responses. Include it when
contacting support to help us trace the issue.
Handling Rate Limits
When you receive a 429 response, implement exponential backoff:
import time
import requests
def call_with_retry(url, headers, json_data, max_retries=3):
for attempt in range(max_retries):
response = requests.post(url, headers=headers, json=json_data)
if response.status_code == 429:
wait = 2 ** attempt # 1s, 2s, 4s
print(f"Rate limited. Retrying in {wait}s...")
time.sleep(wait)
continue
return response
raise Exception("Max retries exceeded")Context Headers
After successful authentication, the gateway injects context headers that flow through the entire request chain. These are useful for debugging and tracing:
| Header | Description | Example |
|---|---|---|
X-Tenant-ID | Your tenant UUID | a1b2c3d4-e5f6-7890-abcd-ef1234567890 |
X-Request-ID | Unique request trace ID | f6a7b8c9-d0e1-2345-6789-abcdef012345 |
X-API-Key-Version | Version of the API key used | 3 |
You can pass your own X-Request-ID header in the request, and the gateway will
propagate it. If you do not provide one, the gateway generates a UUID automatically.
This is useful for correlating requests across your own logging infrastructure.
Security Best Practices
Store Keys in Environment Variables
Never hardcode API keys in your source code. Use environment variables or a secrets manager:
# Set the environment variable
export QPHER_API_KEY="qph_your_key_here"
import os
api_key = os.environ["QPHER_API_KEY"]
Never Commit Keys to Source Control
Add API keys to your .gitignore and use tools like git-secrets or pre-commit
hooks to prevent accidental commits:
# .gitignore
.env
.env.local
*.key
Use Separate Keys Per Environment
Create distinct API keys for development, staging, and production. If a dev key is compromised, your production data remains safe.
Rotate Keys Regularly
Rotate your API keys periodically and immediately after any suspected compromise. Qpher supports zero-downtime rotation -- the new key becomes active while the old key remains valid for a brief transition window before automatic revocation.
curl -X POST https://api.qpher.ai/api/v1/tenants/{tenant_id}/api-keys/rotate \
-H "X-API-Key: qph_your_key_here"
Restrict Key Permissions
Use the Policy Engine to restrict what each API key can do. For example, a key used only for encryption should not have access to key management endpoints.
When you rotate an API key, the old key is revoked after the transition window. Revoked keys cannot be reactivated. Ensure all systems are updated to use the new key before the window closes.
Portal Authentication (JWT)
The Qpher User Portal uses a separate authentication mechanism based on JSON Web Tokens (JWT) with httpOnly cookies.
How It Differs from API Key Auth
| Aspect | API Key | Portal JWT |
|---|---|---|
| Used by | Your application code | Browser sessions on portal.qpher.ai |
| Transport | X-API-Key header | httpOnly cookie (automatic) |
| Lifetime | Until rotated or revoked | Access: 24 hours, Refresh: 7 days |
| Scope | PQC operations + key management | Dashboard, settings, billing |
Dual-Auth Gateway
The API Gateway supports both authentication methods. It determines which method
to use based on the presence of the X-API-Key header:
X-API-Keypresent -- API key authentication (for programmatic access)- No
X-API-Key, valid JWT cookie -- Portal session authentication (for browser access)
You do not need to manage JWT tokens directly. The portal handles token refresh and cookie management automatically.
Unauthenticated Endpoints
The following endpoints are publicly accessible and do not require authentication:
| Endpoint | Purpose |
|---|---|
GET /health | Service health check |
GET /metrics | Prometheus metrics (typically restricted by network policy) |
GET /docs | OpenAPI documentation UI |
GET /openapi.json | OpenAPI specification |
Next Steps
- Core Concepts -- Understand tenants, key versioning, and encryption
- Quickstart Guide -- Make your first authenticated API call
- API Reference -- Full endpoint documentation
- Error Codes -- Complete error code reference