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
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-SignatureandX-Timestampheaders
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 secondspayload: 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