API Keys & Function Signing
ContainerPub provides an API key system for securing function invocations with cryptographic signatures. This ensures that only authorized clients can invoke your functions.
Overview
The API key system uses HMAC-SHA256 signatures to authenticate requests:
- Public Key: Stored on the server, used to verify signatures
- Private Key: Returned only once at creation, stored locally by the CLI
- Signature: Created by hashing the payload with the private key
The private key is only shown once when generated. Store it securely!
Key Features
- Per-function keys: Each function can have its own API key
- Configurable validity: 1 hour, 1 day, 1 week, 1 month, or forever
- Replay protection: 5-minute timestamp window prevents replay attacks
- Automatic expiration: Keys expire based on validity setting
- One active key: Generating a new key deactivates the previous one
Validity Options
| Option | Duration | Use Case |
|---|---|---|
| 1h | 1 hour | Testing, temporary access |
| 1d | 1 day | Daily rotation |
| 1w | 1 week | Weekly rotation |
| 1m | 1 month | Monthly rotation |
| forever | Never expires | Production (rotate manually) |
CLI Commands
Generate API Key
Generate a new API key for a deployed function:
# From function directory (uses function_config.json)
dart_cloud apikey generate --validity 1d
# With custom name
dart_cloud apikey generate --validity 1w --name "Production Key"
# For specific function
dart_cloud apikey generate --function-id <uuid> --validity 1m
Output:
✓ API key generated successfully! ╔════════════════════════════════════════════════════════════════════╗ ║ ⚠️ IMPORTANT: Store the private key securely! ║ ║ It will NOT be shown again. ║ ╚════════════════════════════════════════════════════════════════════╝ Key UUID: abc123-def456-... Public Key: YWJjZGVmZ2hpams... Private Key: eHl6MTIzNDU2Nzg5... Validity: 1d Expires At: 2025-12-16T02:00:00Z ✓ Private key saved to .dart_tool/api_key.secret ✓ Function config updated with API key info
View API Key Info
Check the current API key status:
dart_cloud apikey info
# For specific function
dart_cloud apikey info --function-id <uuid>
List All Keys
View API key history for a function:
dart_cloud apikey list
# For specific function
dart_cloud apikey list --function-id <uuid>
Roll API Key
Extend an API key's expiration by its validity period:
# Roll key from current directory config
dart_cloud apikey roll
# Roll specific key
dart_cloud apikey roll --key-id <api-key-uuid>
This is useful for extending the lifetime of an active key without regenerating it. The expiration is extended by the key's validity period (e.g., if validity is 1d, it adds 1 day to the current expiration).
Revoke API Key
Revoke an active API key:
# Revoke key from current directory config
dart_cloud apikey revoke
# Revoke specific key
dart_cloud apikey revoke --key-id <api-key-uuid>
Invoking with Signature
Use the --sign flag to sign requests with your API key:
dart_cloud invoke <function-id> --data '{"key": "value"}' --sign
The CLI will:
- Load the private key from .dart_tool/api_key.secret
- Create a timestamp
- Generate HMAC-SHA256 signature
- Include X-Signature and X-Timestamp headers
Signature Algorithm
The signature is created using HMAC-SHA256:
// Data to sign
String dataToSign = "$timestamp:$payload";
// Create signature
var hmac = Hmac(sha256, utf8.encode(privateKey));
var digest = hmac.convert(utf8.encode(dataToSign));
String signature = base64Encode(digest.bytes);
Components:
- timestamp: Unix timestamp in seconds
- payload: JSON-encoded request body (empty string if no body)
- privateKey: Your API key private key
HTTP Request Format
When invoking a signed function via HTTP:
POST /api/functions/<function-id>/invoke HTTP/1.1
Host: api.containerpub.dev
Authorization: Bearer <access-token>
Content-Type: application/json
X-Signature: <base64-signature>
X-Timestamp: <unix-timestamp>
{
"body": {
"key": "value"
}
}
Security Considerations
Timestamp Window
Requests are only valid within a 5-minute window of the timestamp. This prevents replay attacks where an attacker captures and resends a valid request.
Key Storage
- Private key: Stored in .dart_tool/api_key.secret
- Auto-gitignore: The CLI automatically adds this file to .gitignore
- Never commit: Never commit private keys to version control
Key Rotation
Best practices for key rotation:
- Generate new key before old one expires
- Update all clients with new private key
- Old key is automatically deactivated
- Monitor for failed signature verifications
API Endpoints
POST /api/auth/apikey/generate
Generate a new API key for a function.
Headers:
Authorization: Bearer <access-token>
Content-Type: application/json
Request:
{
"function_id": "function-uuid",
"validity": "1d",
"name": "Optional key name"
}
Response:
{
"message": "API key generated successfully",
"warning": "Store the private_key securely - it will not be shown again!",
"api_key": {
"uuid": "key-uuid",
"public_key": "base64-public-key",
"private_key": "base64-private-key",
"validity": "1d",
"expires_at": "2025-12-16T02:00:00Z",
"created_at": "2025-12-15T02:00:00Z"
}
}
GET /api/auth/apikey/:function_id
Get API key info for a function (without private key).
Headers:
Authorization: Bearer <access-token>Response:
{
"has_api_key": true,
"api_key": {
"uuid": "key-uuid",
"public_key": "base64-public-key",
"validity": "1d",
"expires_at": "2025-12-16T02:00:00Z",
"is_active": true,
"created_at": "2025-12-15T02:00:00Z"
}
}
PUT /api/auth/apikey/:api_key_uuid/roll
Extend an API key's expiration by its validity period.
Headers:
Authorization: Bearer <access-token>Response:
{
"message": "API key updated successfully"
}
What it does:
- Extends the expiration date by the key's validity period
- For example, if validity is 1d, it adds 1 day to the current expiration
- Useful for extending active keys without regenerating them
- Does not change the key UUID or secret
DELETE /api/auth/apikey/:api_key_uuid
Revoke an API key.
Headers:
Authorization: Bearer <access-token>Response:
{
"message": "API key revoked successfully"
}
GET /api/auth/apikey/:function_id/list
List all API keys for a function (history).
Headers:
Authorization: Bearer <access-token>Response:
{
"api_keys": [
{
"uuid": "key-uuid-1",
"public_key": "base64-public-key",
"validity": "1d",
"expires_at": "2025-12-16T02:00:00Z",
"is_active": true,
"created_at": "2025-12-15T02:00:00Z"
},
{
"uuid": "key-uuid-2",
"public_key": "base64-public-key",
"validity": "1w",
"expires_at": null,
"is_active": false,
"revoked_at": "2025-12-14T00:00:00Z",
"created_at": "2025-12-07T00:00:00Z"
}
]
}
Error Responses
Missing Signature (403)
When a function requires API key but signature is missing:
{
"error": "This function requires API key signature",
"message": "Include X-Signature and X-Timestamp headers"
}
Invalid Signature (403)
When signature verification fails:
{
"error": "Invalid signature",
"message": "Signature verification failed. Check your API key and timestamp."
}
Expired Timestamp (403)
When timestamp is outside the 5-minute window:
{
"error": "Invalid signature",
"message": "Signature verification failed. Check your API key and timestamp."
}
Database Schema
The api_keys table stores API key metadata:
CREATE TABLE api_keys (
id SERIAL PRIMARY KEY,
uuid UUID UNIQUE NOT NULL DEFAULT uuid_generate_v4(),
function_uuid UUID NOT NULL REFERENCES functions(uuid) ON DELETE CASCADE,
public_key TEXT NOT NULL,
private_key_hash VARCHAR(255),
validity VARCHAR(20) NOT NULL CHECK (validity IN ('1h', '1d', '1w', '1m', 'forever')),
expires_at TIMESTAMP WITH TIME ZONE,
is_active BOOLEAN DEFAULT true,
name VARCHAR(255),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
revoked_at TIMESTAMP WITH TIME ZONE
);
Example: Programmatic Signing
If you need to sign requests programmatically (not using the CLI):
import 'dart:convert';
import 'package:crypto/crypto.dart';
String createSignature({
required String privateKey,
required String payload,
required int timestamp,
}) {
final dataToSign = '$timestamp:$payload';
final hmac = Hmac(sha256, utf8.encode(privateKey));
final digest = hmac.convert(utf8.encode(dataToSign));
return base64Encode(digest.bytes);
}
// Usage
final timestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final payload = jsonEncode({'key': 'value'});
final signature = createSignature(
privateKey: 'your-private-key',
payload: payload,
timestamp: timestamp,
);
// Include in request headers
// X-Signature: $signature
// X-Timestamp: $timestamp
Next Steps
- Read Authentication for token-based auth
- Check Function Execution for invocation details
- Explore CLI Reference for all commands