API Keys Management Endpoints
Relevant source files
The following files were used as context for generating this wiki page:
The API Keys Management system in Send Again provides programmatic access to the platform's core features, such as contact management and email dispatch. These endpoints allow users to create, list, and revoke scoped credentials that authenticate requests without requiring a full Supabase JWT session.
Authentication for these management endpoints themselves strictly requires a Supabase JWT (Bearer token), ensuring that only authenticated users can manage the programmatic access for their workspaces. Once created, these keys (identifiable by the sk_ prefix) can be used on /api/contacts and /api/send* endpoints.
Sources: src/lib/openapi.ts:15-22, src/lib/auth.ts:100-115
Key Lifecycle and Security Architecture
The system utilizes a secure storage mechanism where the full plaintext key is never stored in the database. Instead, a SHA-256 hash is used for verification, and a short prefix is stored for display purposes in the UI.
Data Flow for Key Creation
When a user requests a new API key, the system generates a random 32-byte value, hashes it, and stores the metadata along with assigned scopes.
The creation process ensures that the plaintext key is only returned once during the initial POST response. Subsequent retrievals via GET only return metadata.
Sources: src/lib/db.ts:145-165, src/lib/openapi.ts:48-65, src/app/api/keys/route.ts:45-52
Database Representation
API keys are linked to specific workspaces. The api_keys table tracks the relationship between the hashed credential and the workspace permissions.
| Field | Type | Description |
|---|---|---|
id | UUID | Unique identifier for the key record. |
workspace_id | String | The domain/workspace this key belongs to. |
name | String | A user-defined label for the key (e.g., "CI Pipeline"). |
key_prefix | String | The first 10 characters of the key (sk_...). |
key_hash | String | SHA-256 hash of the full plaintext key. |
scopes | Array | List of permitted operations (e.g., send.write). |
Sources: src/lib/db.ts:120-135, src/lib/openapi.ts:28-45
Endpoint Definitions
1. List API Keys (GET /api/keys)
Retrieves all API keys associated with a specific workspace. This endpoint requires a workspace query parameter.
- Security: Requires Supabase JWT.
- Access Check: The user must have access to the workspace specified in the query string.
- Behavior: Returns an array of metadata. The
key_hashand plaintext value are omitted.
Sources: src/app/api/keys/route.ts:57-75, src/lib/openapi.ts:215-230
2. Create API Key (POST /api/keys)
Generates a new scoped API key for a workspace.
- Parameters:
workspace(Required): The ID of the target workspace.name(Optional): A descriptive name.scopes(Optional): An array of strings defining permissions.
- Default Behavior: If scopes are omitted, the key is granted full access for backward compatibility.
Sources: src/app/api/keys/route.ts:11-44, src/lib/openapi.ts:231-265
3. Revoke API Key (DELETE /api/keys/{id})
Permanently deletes an API key, immediately invalidating any programmatic access using that credential.
API Scopes and Permissions
Keys can be limited to specific actions to follow the principle of least privilege. Scopes are enforced during the requireWorkspaceAuth check when an API key is used as a Bearer token.
| Scope | Permission |
|---|---|
contacts.read | View contact lists and individual contact details. |
contacts.write | Create, update, or delete contacts. |
send.read | View send job statuses and history. |
send.write | Enqueue new email sending jobs. |
Sources: src/lib/openapi.ts:38-43, src/lib/auth.ts:117-130, src/app/app/page.tsx:135-140
Implementation Details
Key Generation Logic
The key is prefixed with sk_ to follow common API industry standards for secret keys.
// src/lib/db.ts
function generateRawApiKey(): string {
const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);
const hex = Array.from(bytes)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
return `sk_${hex}`;
}
Sources: src/lib/db.ts:145-152
Hashing Strategy
The system uses the SubtleCrypto API to perform SHA-256 hashing, ensuring high-performance cryptographic operations within the runtime.
// src/lib/db.ts
async function hashApiKey(raw: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(raw);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
}
Sources: src/lib/db.ts:137-143
Summary
The API Key Management endpoints provide a robust framework for securing programmatic interaction with the Send Again platform. By combining Supabase JWT for management, SHA-256 hashing for storage, and granular scopes for enforcement, the system ensures that automated workflows can operate safely without exposing user credentials.
Developers should remember that the plaintext sk_ key is visible only once upon creation and should be stored securely in environment variables or secret managers.