API Key Management
| Field | Value |
|---|---|
| Document ID | ASCEND-SEC-001 |
| Version | 1.0.0 |
| Last Updated | December 19, 2025 |
| Author | Ascend Engineering Team |
| Publisher | OW-KAI Technologies Inc. |
| Classification | Enterprise Client Documentation |
| Compliance | SOC 2 CC6.1/CC6.2, PCI-DSS 7.1/8.3, HIPAA 164.312, NIST 800-53 AC-2/SI-4 |
Reading Time: 10 minutes | Skill Level: Intermediate
Overview
ASCEND provides enterprise-grade API key management for SDK authentication. Keys use SHA-256 hashing with salt, constant-time comparison, rate limiting, and complete audit trails.
Key Architecture
+---------------------------------------------------------------------------------+
| API KEY SECURITY ARCHITECTURE |
+---------------------------------------------------------------------------------+
| |
| KEY GENERATION |
| +-------------------------------------------------------------------------+ |
| | | |
| | 1. Generate 256-bit random key (secrets.token_urlsafe(32)) | |
| | 2. Add role prefix (owkai_admin_, owkai_user_) | |
| | 3. Generate 128-bit salt | |
| | 4. SHA-256 hash (key + salt) → key_hash | |
| | 5. Store: key_hash, salt, key_prefix (first 32 chars) | |
| | | |
| | NEVER stored: full plaintext key (shown once on generation) | |
| | | |
| +-------------------------------------------------------------------------+ |
| |
| KEY VERIFICATION |
| +-------------------------------------------------------------------------+ |
| | | |
| | 1. Extract prefix from provided key | |
| | 2. Lookup by prefix (may return multiple candidates) | |
| | 3. For each candidate: SHA-256(key + salt) == stored hash? | |
| | 4. Constant-time comparison (secrets.compare_digest) | |
| | 5. Check: is_active, expires_at, rate_limit | |
| | | |
| +-------------------------------------------------------------------------+ |
| |
+---------------------------------------------------------------------------------+
Generating API Keys
REST API
curl -X POST "https://pilot.owkai.app/api/keys/generate" \
-H "Authorization: Bearer <jwt_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Production SDK Key",
"description": "Main production API key for agent actions",
"expires_in_days": 90,
"permissions": [
{"category": "agent", "actions": ["read", "write"]},
{"category": "action", "actions": ["submit", "read"]}
],
"rate_limit": {
"max_requests": 1000,
"window_seconds": 3600
}
}'
Response:
{
"success": true,
"api_key": "owkai_admin_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3",
"key_id": 123,
"key_prefix": "owkai_admin_a1b2c3d4e5f6g7h8",
"name": "Production SDK Key",
"expires_at": "2026-03-15T10:30:00Z",
"created_at": "2025-12-15T10:30:00Z",
"warning": "⚠️ Save this key now - you will NOT see it again!"
}
Permission Shorthand
# Use string shorthand for permissions
curl -X POST "https://pilot.owkai.app/api/keys/generate" \
-H "Authorization: Bearer <jwt_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Read-Only Key",
"permissions": ["agent:read", "action:read", "analytics:read"]
}'
Using API Keys
Authorization Header
curl "https://pilot.owkai.app/api/v1/actions/submit" \
-H "Authorization: Bearer owkai_admin_a1b2c3d4..."
X-API-Key Header
curl "https://pilot.owkai.app/api/v1/actions/submit" \
-H "X-API-Key: owkai_admin_a1b2c3d4..."
SDK Integration
from ascend import AscendClient
# Initialize with API key
client = AscendClient(api_key="owkai_admin_a1b2c3d4...")
# Submit action
result = client.evaluate_action(
action_type="database.query",
resource="customers"
)
Listing API Keys
curl "https://pilot.owkai.app/api/keys/list?include_revoked=false&page=1" \
-H "Authorization: Bearer <jwt_token>"
Response:
{
"success": true,
"keys": [
{
"id": 123,
"name": "Production SDK Key",
"key_prefix": "owkai_admin_a1b2c3d4",
"is_active": true,
"expires_at": "2026-03-15T10:30:00Z",
"last_used_at": "2025-12-15T10:25:00Z",
"usage_count": 15420,
"created_at": "2025-12-15T10:30:00Z"
}
],
"total_count": 1,
"page": 1,
"page_size": 20
}
Revoking API Keys
curl -X DELETE "https://pilot.owkai.app/api/keys/123/revoke?reason=Security%20rotation" \
-H "Authorization: Bearer <jwt_token>"
Response:
{
"success": true,
"message": "API key revoked successfully",
"key_id": 123,
"revoked_at": "2025-12-15T10:35:00Z"
}
Usage Statistics
curl "https://pilot.owkai.app/api/keys/123/usage?limit=100" \
-H "Authorization: Bearer <jwt_token>"
Response:
{
"success": true,
"key_id": 123,
"key_prefix": "owkai_admin_a1b2c3d4",
"statistics": {
"total_requests": 15420,
"recent_requests": 100,
"success_rate": 98.5,
"last_used_at": "2025-12-15T10:25:00Z"
},
"recent_activity": [
{
"timestamp": "2025-12-15T10:25:00Z",
"endpoint": "/api/v1/actions/submit",
"method": "POST",
"status": 200,
"ip_address": "192.168.1.100",
"response_time_ms": 45
}
]
}
Rate Limiting
Default Limits
| Tier | Max Requests | Window |
|---|---|---|
| Standard | 1,000 | 1 hour |
| Premium | 10,000 | 1 hour |
| Enterprise | Custom | Custom |
Rate Limit Response
When rate limit is exceeded:
{
"detail": "Rate limit exceeded",
"retry_after": 3600
}
Headers:
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
Configure Custom Limits
curl -X POST "https://pilot.owkai.app/api/keys/generate" \
-H "Authorization: Bearer <jwt_token>" \
-d '{
"name": "High-Volume Key",
"rate_limit": {
"max_requests": 10000,
"window_seconds": 3600
}
}'
Permissions
Permission Categories
| Category | Actions | Description |
|---|---|---|
agent | read, write, delete | Agent management |
action | submit, read, approve, deny | Action governance |
analytics | read | View analytics |
audit | read, export | Audit log access |
config | read, write | Configuration |
Permission Examples
{
"permissions": [
{"category": "agent", "actions": ["read"]},
{"category": "action", "actions": ["submit", "read"]},
{"category": "analytics", "actions": ["read"]}
]
}
Read-Only Key
{
"permissions": ["agent:read", "action:read", "analytics:read"]
}
Full Access Key
{
"permissions": [
{"category": "agent", "actions": ["read", "write", "delete"]},
{"category": "action", "actions": ["submit", "read", "approve", "deny"]},
{"category": "analytics", "actions": ["read"]},
{"category": "audit", "actions": ["read", "export"]},
{"category": "config", "actions": ["read", "write"]}
]
}
Security Features
Constant-Time Comparison
# Source: dependencies_api_keys.py:107
# Prevents timing attacks
if secrets.compare_digest(candidate_hash, candidate.key_hash):
matched_key = candidate
RLS Context Setting
# Source: dependencies_api_keys.py:141
# Set organization context for row-level security
db.execute(
text("SET LOCAL app.current_organization_id = :org_id"),
{"org_id": str(api_key_org_id)}
)
Audit Logging
All API key operations are logged:
{
"event_type": "api_key_generated",
"actor_id": "admin@company.com",
"resource_type": "api_key",
"resource_id": 123,
"outcome": "success",
"metadata": {
"key_prefix": "owkai_admin_a1b2c3d4",
"key_name": "Production SDK Key",
"expires_at": "2026-03-15T10:30:00Z",
"permissions_count": 3
}
}
Data Model
# Source: models_api_keys.py:23
class ApiKey(Base):
"""Enterprise API key storage with cryptographic security."""
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"))
organization_id = Column(Integer, ForeignKey("organizations.id"))
# Cryptographic storage (NEVER stores plaintext)
key_hash = Column(String(64)) # SHA-256 hash
key_prefix = Column(String(40)) # First 32 chars for lookup
salt = Column(String(32)) # Random salt
# Metadata
name = Column(String(255))
description = Column(Text)
is_active = Column(Boolean)
expires_at = Column(DateTime)
# Usage tracking
last_used_at = Column(DateTime)
usage_count = Column(BigInteger)
Best Practices
1. Rotate Keys Regularly
# Set expiration for automatic rotation reminders
{
"expires_in_days": 90
}
2. Use Minimal Permissions
# Only grant necessary permissions
{
"permissions": ["action:submit", "action:read"]
}
3. Monitor Usage
# Check for unusual patterns
usage = client.keys.get_usage(key_id)
if usage.success_rate < 90:
investigate_errors()
4. Revoke Unused Keys
# Revoke keys not used in 30 days
for key in client.keys.list():
if key.last_used_at < thirty_days_ago:
client.keys.revoke(key.id, reason="Unused key cleanup")
5. Store Keys Securely
# Use environment variables or secrets manager
import os
api_key = os.environ.get("ASCEND_API_KEY")
# Never commit keys to source control
# Never log full key values
Next Steps
- Authentication — JWT and session management
- SSO — Enterprise SSO integration
- Multi-Tenancy — Organization isolation
Document Version: 1.0.0 | Last Updated: December 2025