Skip to main content

Create API Key

Store an API key for an AI provider. Keys are encrypted using AES-256-GCM before storage when an ENCRYPTION_KEY environment variable is configured.

Authentication

Requires authentication via session cookie or bearer token.

Encryption

API keys are protected using multiple layers of security:
  1. AES-256-GCM Encryption: When ENCRYPTION_KEY is configured, keys are encrypted with a 256-bit key using Galois/Counter Mode, which provides authenticated encryption
  2. SHA-256 Hash: A hash of the key is stored for validation without exposing the plaintext
  3. Fallback: If no encryption key is configured, keys are hex-encoded (not recommended for production)
The encryption implementation uses a random 12-byte nonce for each encryption operation, ensuring different ciphertexts even for identical keys. The encrypted format is base64(nonce || ciphertext || tag).
Always configure ENCRYPTION_KEY in production. Without it, API keys are stored with hex encoding only, which provides minimal security.

Request Body

provider
string
required
AI provider name. Must be one of: anthropic, openai, ollama
key
string
required
The API key or access token for the provider. Must not be empty.
label
string
Optional human-readable label for the key (e.g., “Production Key”, “Development”)

Response

id
string
UUID of the created API key record
provider
string
The provider name (anthropic, openai, or ollama)
label
string
The optional label provided during creation
key_preview
string
Masked preview of the key for identification. Shows first 7 and last 3 characters for keys ≥12 characters, or first 3 and last 2 for shorter keys.
created_at
string
ISO 8601 timestamp of when the key was created

Example

curl -X POST https://api.heimdall.dev/api/settings/api-keys \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "anthropic",
    "key": "sk-ant-api03-...",
    "label": "Production Key"
  }'

Response Example

{
  "status": "ok",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "provider": "anthropic",
    "label": "Production Key",
    "key_preview": "sk-ant-***440",
    "created_at": "2026-03-12T10:30:00Z"
  }
}

Error Responses

400 Bad Request:
  • Unsupported provider (must be anthropic, openai, or ollama)
  • Key value is empty

Delete API Key

Soft-delete an API key. The key is marked as deleted in the database but not permanently removed.

Authentication

Requires authentication via session cookie or bearer token.

Path Parameters

id
string
required
UUID of the API key to delete

Response

deleted
boolean
Always true on successful deletion
id
string
UUID of the deleted API key

Example

curl -X DELETE https://api.heimdall.dev/api/settings/api-keys/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer YOUR_TOKEN"

Response Example

{
  "status": "ok",
  "data": {
    "deleted": true,
    "id": "550e8400-e29b-41d4-a716-446655440000"
  }
}

Error Responses

404 Not Found: API key not found or already deleted

Security Notes

  • Keys are soft-deleted (marked as deleted, not removed from database)
  • Users can only delete their own API keys
  • Deleted keys are no longer available for AI operations

Encryption Implementation Details

The encryption system is implemented in src/crypto.rs and provides:

AES-256-GCM Encryption

  • Algorithm: AES-256-GCM (Galois/Counter Mode)
  • Key Size: 256 bits (32 bytes)
  • Nonce: 12 bytes, randomly generated per encryption
  • Format: base64(nonce || ciphertext || tag)
  • Tag Size: 16 bytes (automatically appended by GCM)

Key Masking

Keys are masked for display using the mask_key function (src/routes/settings.rs:105):
  • Keys ≥12 characters: Shows first 7 + *** + last 3 (e.g., sk-ant-***440)
  • Keys 7-11 characters: Shows first 3 + *** + last 2
  • Keys ≤6 characters: Shows *** only

Key Hashing

A SHA-256 hash of each key is stored for validation purposes without requiring decryption. The hash is computed using the hash_key function (src/routes/settings.rs:120).

Decryption Fallback

The decode_stored_secret function (src/crypto.rs:53) supports multiple formats for backward compatibility:
  1. AES-256-GCM encrypted (preferred): Attempts decryption with configured key
  2. Hex-encoded: Falls back to hex decoding if decryption fails
  3. Plaintext: Returns as-is if no encryption key is configured (development only)
This multi-format support allows for gradual migration from hex-encoded keys to AES-256-GCM encryption without service disruption.

Implementation Reference

See src/routes/settings.rs:128 (encrypt_key function), src/routes/settings.rs:239 (create endpoint), src/routes/settings.rs:314 (delete endpoint), and src/crypto.rs:16 (encryption implementation).

Build docs developers (and LLMs) love