- Replace orderByRaw with parameterised CASE statements - Add Task::scopeOrderByPriority() and scopeOrderByStatus() - Add AgentPlan::scopeOrderByStatus() - Add workspace validation to StateSet, StateGet, StateList tools - Add workspace validation to PlanGet, PlanList tools - Add SecurityTest.php with comprehensive isolation tests Fixes SEC-002, SEC-003 from security audit. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
7 KiB
7 KiB
| title | description | updated |
|---|---|---|
| API Keys | Guide to Agent API key management | 2026-01-29 |
API Key Management
Agent API keys provide authenticated access to the MCP tools and agentic services. This guide covers key creation, permissions, and security.
Key Structure
API keys follow the format: ak_ + 32 random alphanumeric characters.
Example: ak_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
The key is only displayed once at creation. Store it securely.
Creating Keys
Via Admin Panel
- Navigate to Workspace Settings > API Keys
- Click "Create New Key"
- Enter a descriptive name
- Select permissions
- Set expiration (optional)
- Click Create
- Copy the displayed key immediately
Programmatically
use Core\Mod\Agentic\Services\AgentApiKeyService;
$service = app(AgentApiKeyService::class);
$key = $service->create(
workspace: $workspace,
name: 'My Agent Key',
permissions: [
AgentApiKey::PERM_PLANS_READ,
AgentApiKey::PERM_PLANS_WRITE,
AgentApiKey::PERM_SESSIONS_WRITE,
],
rateLimit: 100,
expiresAt: now()->addYear()
);
// Only available once
$plainKey = $key->plainTextKey;
Permissions
Available Permissions
| Permission | Constant | Description |
|---|---|---|
plans.read |
PERM_PLANS_READ |
List and view plans |
plans.write |
PERM_PLANS_WRITE |
Create, update, archive plans |
phases.write |
PERM_PHASES_WRITE |
Update phases, manage tasks |
sessions.read |
PERM_SESSIONS_READ |
List and view sessions |
sessions.write |
PERM_SESSIONS_WRITE |
Start, update, end sessions |
tools.read |
PERM_TOOLS_READ |
View tool analytics |
templates.read |
PERM_TEMPLATES_READ |
List and view templates |
templates.instantiate |
PERM_TEMPLATES_INSTANTIATE |
Create plans from templates |
notify:read |
PERM_NOTIFY_READ |
List push campaigns |
notify:write |
PERM_NOTIFY_WRITE |
Create/update campaigns |
notify:send |
PERM_NOTIFY_SEND |
Send notifications |
Permission Checking
// Single permission
$key->hasPermission('plans.write');
// Any of several
$key->hasAnyPermission(['plans.read', 'sessions.read']);
// All required
$key->hasAllPermissions(['plans.write', 'phases.write']);
Updating Permissions
$service->updatePermissions($key, [
AgentApiKey::PERM_PLANS_READ,
AgentApiKey::PERM_SESSIONS_READ,
]);
Rate Limiting
Configuration
Each key has a configurable rate limit (requests per minute):
$key = $service->create(
workspace: $workspace,
name: 'Limited Key',
permissions: [...],
rateLimit: 50 // 50 requests/minute
);
// Update later
$service->updateRateLimit($key, 100);
Checking Status
$status = $service->getRateLimitStatus($key);
// Returns:
// [
// 'limit' => 100,
// 'remaining' => 85,
// 'reset_in_seconds' => 45,
// 'used' => 15
// ]
Response Headers
Rate limit info is included in API responses:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 85
X-RateLimit-Reset: 45
When rate limited (HTTP 429):
Retry-After: 45
IP Restrictions
Keys can be restricted to specific IP addresses or ranges.
Enabling Restrictions
// Enable with whitelist
$service->enableIpRestrictions($key, [
'192.168.1.0/24', // CIDR range
'10.0.0.5', // Single IPv4
'2001:db8::1', // Single IPv6
'2001:db8::/32', // IPv6 CIDR
]);
// Disable restrictions
$service->disableIpRestrictions($key);
Managing Whitelist
// Add single entry
$key->addToIpWhitelist('192.168.2.0/24');
// Remove entry
$key->removeFromIpWhitelist('192.168.1.0/24');
// Replace entire list
$key->updateIpWhitelist([
'10.0.0.0/8',
'172.16.0.0/12',
]);
Parsing Input
For user-entered whitelists:
$result = $service->parseIpWhitelistInput("
192.168.1.1
192.168.2.0/24
# This is a comment
invalid-ip
");
// Result:
// [
// 'entries' => ['192.168.1.1', '192.168.2.0/24'],
// 'errors' => ['invalid-ip: Invalid IP address']
// ]
Key Lifecycle
Expiration
// Set expiration on create
$key = $service->create(
...
expiresAt: now()->addMonths(6)
);
// Extend expiration
$service->extendExpiry($key, now()->addYear());
// Remove expiration (never expires)
$service->removeExpiry($key);
Revocation
// Immediately revoke
$service->revoke($key);
// Check status
$key->isRevoked(); // true
$key->isActive(); // false
Status Helpers
$key->isActive(); // Not revoked, not expired
$key->isRevoked(); // Has been revoked
$key->isExpired(); // Past expiration date
$key->getStatusLabel(); // "Active", "Revoked", or "Expired"
Authentication
Making Requests
Include the API key as a Bearer token:
curl -H "Authorization: Bearer ak_your_key_here" \
https://mcp.host.uk.com/api/agent/plans
Authentication Flow
- Middleware extracts Bearer token
- Key looked up by SHA-256 hash
- Status checked (revoked, expired)
- IP validated if restrictions enabled
- Permissions checked against required scopes
- Rate limit checked and incremented
- Usage recorded (count, timestamp, IP)
Error Responses
| HTTP Code | Error | Description |
|---|---|---|
| 401 | unauthorised |
Missing or invalid key |
| 401 | key_revoked |
Key has been revoked |
| 401 | key_expired |
Key has expired |
| 403 | ip_not_allowed |
Request IP not whitelisted |
| 403 | permission_denied |
Missing required permission |
| 429 | rate_limited |
Rate limit exceeded |
Usage Tracking
Each key tracks:
call_count- Total lifetime callslast_used_at- Timestamp of last uselast_used_ip- IP of last request
Access via:
$key->call_count;
$key->getLastUsedForHumans(); // "2 hours ago"
Best Practices
- Use descriptive names - "Production Agent" not "Key 1"
- Minimal permissions - Only grant needed scopes
- Set expiration - Rotate keys periodically
- Enable IP restrictions - When agents run from known IPs
- Monitor usage - Review call patterns regularly
- Revoke promptly - If key may be compromised
- Separate environments - Different keys for dev/staging/prod
Example: Complete Setup
use Core\Mod\Agentic\Services\AgentApiKeyService;
use Core\Mod\Agentic\Models\AgentApiKey;
$service = app(AgentApiKeyService::class);
// Create a production key
$key = $service->create(
workspace: $workspace,
name: 'Production Agent - Claude',
permissions: [
AgentApiKey::PERM_PLANS_READ,
AgentApiKey::PERM_PLANS_WRITE,
AgentApiKey::PERM_PHASES_WRITE,
AgentApiKey::PERM_SESSIONS_WRITE,
AgentApiKey::PERM_TEMPLATES_READ,
AgentApiKey::PERM_TEMPLATES_INSTANTIATE,
],
rateLimit: 200,
expiresAt: now()->addYear()
);
// Restrict to known IPs
$service->enableIpRestrictions($key, [
'203.0.113.0/24', // Office network
'198.51.100.50', // CI/CD server
]);
// Store the key securely
$plainKey = $key->plainTextKey; // Only chance to get this!