API Keys
API keys provide authentication for automation, scripts, and third-party applications like OPDS readers.
Key Concepts
- API keys are scoped to permissions - Each key has a specific set of permissions
- Keys cannot exceed user permissions - You can only grant permissions you have
- Keys are constrained at request time - If your role changes, your keys are automatically limited
- Keys can have expiration dates - Optional time-limited access
Creating API Keys
Via Web Interface
- Go to Settings > Profile > API Keys
- Click Create API Key
- Enter a descriptive name
- Select a permission preset or customize:
- Full Access - All permissions you have
- Read Only - Browse and read content
- OPDS/Reader Apps - Minimal read-only access
- Custom - Select individual permissions
- Optionally set an expiration date
- Click Create
- Copy the key immediately - it's only shown once!
Via API
curl -X POST http://localhost:8080/api/v1/api-keys \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Automation Script",
"permissions": ["LibrariesRead", "BooksRead", "PagesRead"]
}'
Response:
{
"id": "uuid",
"name": "Automation Script",
"key": "codex_abc12345_secretpart123456789",
"key_prefix": "codex_abc12345",
"permissions": ["LibrariesRead", "BooksRead", "PagesRead"],
"expires_at": null,
"created_at": "2024-01-15T10:00:00Z"
}
The full API key is only shown once! Store it securely immediately. If lost, you must create a new key.
Permission Constraints
You Can Only Grant What You Have
API keys are constrained by your effective permissions:
Token Effective Permissions = (Your Role ∪ Your Custom) ∩ Token Requested
Example: A Maintainer (15 permissions) cannot create a key with UsersRead because that permission is Admin-only.
Keys Are Validated at Request Time
If your role is downgraded, your existing keys are automatically constrained:
- Admin creates key with
UsersReadpermission - Admin is demoted to Maintainer
- Key requests to
/api/v1/usersnow fail -UsersReadis no longer in the user's effective permissions
This ensures keys never grant more access than the user currently has.
No Permissions = User's Full Access
If you don't specify permissions when creating a key, it inherits your full effective permissions:
# Creates key with all your permissions
curl -X POST http://localhost:8080/api/v1/api-keys \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Full Access Key"}'
Using API Keys
X-API-Key Header (Recommended)
curl -H "X-API-Key: codex_abc12345_secretpart" \
http://localhost:8080/api/v1/libraries
Bearer Token
curl -H "Authorization: Bearer codex_abc12345_secretpart" \
http://localhost:8080/api/v1/libraries
Basic Auth (for OPDS readers)
Many OPDS reader apps only support Basic Auth:
Username: api
Password: codex_abc12345_secretpart
Or with curl:
curl -u "api:codex_abc12345_secretpart" \
http://localhost:8080/opds/v1.2/catalog
Key Format
API keys follow the format: codex_<prefix>_<secret>
| Part | Description |
|---|---|
codex_ | Fixed prefix identifying Codex keys |
<prefix> | 8-character identifier for quick lookup |
<secret> | Random secret (hashed in database) |
The prefix is stored in plaintext for lookup, but the secret is hashed - Codex cannot recover lost keys.
Permission Presets
OPDS / Reader Apps
Permissions for e-reader apps, OPDS clients, and KOReader sync:
{
"name": "OPDS Reader",
"permissions": ["LibrariesRead", "SeriesRead", "BooksRead", "PagesRead", "ProgressRead", "ProgressWrite"]
}
ProgressRead and ProgressWrite are needed for apps that sync reading progress (e.g., KOReader).
Automation Script
For scripts that trigger scans and monitor progress:
{
"name": "Scanner Script",
"permissions": [
"LibrariesRead",
"LibrariesWrite",
"TasksRead",
"TasksWrite"
]
}
Mobile App
Full reading experience with progress tracking:
{
"name": "Mobile App",
"permissions": [
"LibrariesRead",
"SeriesRead",
"BooksRead",
"BooksWrite",
"PagesRead"
]
}
Metadata Manager
For tools that update metadata:
{
"name": "Metadata Tool",
"permissions": [
"LibrariesRead",
"SeriesRead",
"SeriesWrite",
"BooksRead",
"BooksWrite"
]
}
Managing API Keys
List Your Keys
curl http://localhost:8080/api/v1/api-keys \
-H "Authorization: Bearer $TOKEN"
Response shows key metadata (not the secret):
[
{
"id": "uuid",
"name": "OPDS Reader",
"key_prefix": "codex_abc12345",
"permissions": ["LibrariesRead", "BooksRead"],
"last_used_at": "2024-01-15T12:30:00Z",
"expires_at": null,
"created_at": "2024-01-01T10:00:00Z"
}
]
Revoke a Key
curl -X DELETE http://localhost:8080/api/v1/api-keys/{id} \
-H "Authorization: Bearer $TOKEN"
Revoked keys are immediately invalid.
Key with Expiration
curl -X POST http://localhost:8080/api/v1/api-keys \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Temporary Access",
"permissions": ["LibrariesRead", "BooksRead"],
"expires_at": "2024-02-01T00:00:00Z"
}'
Expired keys are automatically rejected.
Best Practices
- Use minimal permissions - Only grant what the application needs
- Descriptive names - Name keys by purpose (e.g., "Panels iOS App", "Scan Script")
- Separate keys per app - Don't share keys between applications
- Set expiration dates - For temporary or guest access
- Monitor last_used_at - Identify unused keys for cleanup
- Rotate periodically - Create new keys and revoke old ones
- Never commit to VCS - Use environment variables or secret managers
- Revoke compromised keys - Delete immediately if exposed
One key per device
Beyond security, there is a practical reason to give every device its own key: it is what makes per-device reading statistics work.
The Komga-compatible and OPDS protocols have no concept of a device. A request from a comic reader app says which book and which page, and nothing about where it came from. So Codex identifies the device from the API key when there is one, and falls back to a hash of the user agent when there is not.
That fallback works, but it is blunt. Two iPhones running the same app look identical, so their reading merges into one device in your statistics. Give each one its own key and they stay separate, named after the key.
| Setup | How reading appears |
|---|---|
| One key shared by phone and tablet | One device, combined |
| No key (username and password) | One device per app, not per device |
| One key per device | Each device separately, under the key's name |
Name keys after the device rather than the app (Ash's iPhone, not Komic),
since that name is what shows up in the statistics.
KOReader is the exception: it sends its own device identity, so it is attributed correctly regardless of how it authenticates.
Troubleshooting
Key Not Working
- Check the full key - Ensure no truncation or extra whitespace
- Verify not revoked - Check key still exists in your profile
- Check expiration - Key may have expired
- Try different header - Some clients handle headers differently
Permission Denied
- Check key permissions - View key in profile to see its permissions
- Check your role - Your role may have changed since key creation
- Verify endpoint permissions - Confirm which permission the endpoint requires
"Invalid API key format"
The key must start with codex_ and have the correct structure. Verify you're using the complete key.