Authentication & API Keys
Authenticate to the Platform API with workspace keys or device authorization, inspect the live permission catalog, and rotate or revoke credentials safely.
This page covers workspace-scoped API keys and the interactive device code flow. Scoped OAuth2 and provider credentials use the separate flows linked above. Choose the credential accepted by the route; a workspace key, an identity token, and a channel-management token are not interchangeable.
Identity Endpoints
For US production, send workspace resource requests to https://platform.api-us.concurrence.com and identity requests to https://identity.api-us.concurrence.com. Identity owns /token, /device/*, login /sessions, and its discovery/JWKS routes. The workspace text socket /v1/{workspace_id}/sessions/connect remains on Platform. Use the matching deployment slot from Regions and Endpoints.
API Key Usage
For workspace resource routes that accept API keys, send the key as a Bearer credential:
curl https://platform.api-us.concurrence.com/v1/{workspace_id}/agents \
-H "Authorization: Bearer <YOUR_API_KEY>"Each key is scoped to a single workspace. The key's workspace must match the workspace in the request path. A workspace key does not grant access to another workspace. Identity credentials and account-level operations must be evaluated under their own access rules.
API Key Lifecycle
Save a new or rotated key immediately. Its plaintext value is returned only once. Revoke keys that are no longer needed or may have been compromised.
Create an API Key
Create a new API key for a workspace. The response includes the plaintext api_key — store it securely, it cannot be retrieved again.
API key issued via POST /v1/{workspace_id}/api-keys. Pass the returned api_key value as a Bearer token.
memberPossible values: Permission names. Max 128 entries; each entry up to 128 chars.
Successful Response
Missing or invalid API key.
Insufficient permissions or requested role exceeds caller access.
Invalid request body or role.
Canonical API key role configuration unavailable.
POST /v1/{workspace_id}/api-keys HTTP/1.1
Host: platform.api-us.concurrence.com
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 72
{
"name": "text",
"duration_days": 1,
"role": "member",
"permissions": [
"text"
]
}{
"key_id": "text",
"api_key": "text",
"name": "text",
"role": "text",
"permissions": [
"text"
],
"expires_at": "2026-01-01T00:00:00.000Z",
"created_by_entity_id": "123e4567-e89b-12d3-a456-426614174000",
"created_by_credential_id": "123e4567-e89b-12d3-a456-426614174000"
}List API Keys
Filter with role for keys with that role, and with search for a case-insensitive match on the key name or key ID. Filters apply before pagination.
List all API keys for a workspace with pagination. Filter by role, or search a key's name or key ID. Requires ApiKey.view permission.
API key issued via POST /v1/{workspace_id}/api-keys. Pass the returned api_key value as a Bearer token.
[]50falseCase-insensitive match on the key's name or key ID.
Only keys with this role.
Successful Response
Missing or invalid API key.
Insufficient permissions.
Validation Error
Canonical API key role configuration unavailable.
GET /v1/{workspace_id}/api-keys HTTP/1.1
Host: platform.api-us.concurrence.com
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"items": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"workspace_id": "123e4567-e89b-12d3-a456-426614174000",
"created_by_entity_id": "123e4567-e89b-12d3-a456-426614174000",
"created_by_credential_id": "123e4567-e89b-12d3-a456-426614174000",
"key_id": "text",
"name": "text",
"role": "text",
"permissions": [
"text"
],
"expires_at": "2026-01-01T00:00:00.000Z",
"last_used_at": "2026-01-01T00:00:00.000Z",
"created_at": "2026-01-01T00:00:00.000Z",
"updated_at": "2026-01-01T00:00:00.000Z"
}
],
"has_more": true,
"continuation_token": null
}Revoke an API Key
Revoke an API key. Requires ApiKey.delete permission.
API key issued via POST /v1/{workspace_id}/api-keys. Pass the returned api_key value as a Bearer token.
Successful Response
No content
Missing or invalid API key.
Insufficient permissions.
API key not found.
Validation Error
DELETE /v1/{workspace_id}/api-keys/{key_id} HTTP/1.1
Host: platform.api-us.concurrence.com
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
No content
RBAC Roles
Each API key carries a role:
owner
Full access including workspace deletion and ownership transfer
admin
Full read/write to all workspace resources
member
Read/write to most resources; restricted admin operations
operator
Read access plus operator actions: update operator state, control eligible runs, decide conversation-scoped integration approvals, and claim or review external write proposals where that private-preview flow is enabled (no general resource CRUD)
viewer
Read-only access
Attempting an operation beyond the key's role returns 403 Forbidden.
Permission Catalog
Use the live permission catalog instead of hard-coding role defaults in a client.
The caller needs ApiKey:View. Missing or invalid credentials return 401; insufficient permission returns 403.
Return the authoritative role→permission model: each role's default permission set (what an API key of that role may carry) plus the full permission universe. Clients use this to build the create-key form instead of hard-coding the matrix. Requires ApiKey.view permission.
API key issued via POST /v1/{workspace_id}/api-keys. Pass the returned api_key value as a Bearer token.
Successful Response
The authoritative role→permission model for API-key creation.
Serves the server-side source of truth (DEFAULT_ROLE_DEFINITIONS) so
clients (console, SDK) stop hand-copying the matrix and drifting out of
sync — a drift previously shipped Data:Query as a viewer default and
made the default create flow 422. Human-facing labels/descriptions for
individual permissions stay client-side (pure presentation); this payload
is authorization truth only.
Missing or invalid API key.
Insufficient permissions.
GET /v1/{workspace_id}/api-keys/permission-catalog HTTP/1.1
Host: platform.api-us.concurrence.com
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"roles": [
{
"name": "text",
"priority": 1,
"description": "text",
"permission_names": [
"text"
]
}
],
"permissions": [
{
"name": "text",
"namespace": "text",
"action": "text"
}
]
}Key Rotation
Rotation replaces a key's secret atomically. The old secret stops working immediately, and the new plaintext value is returned once.
Replace an API key secret in one step. The old secret stops working immediately, and the response includes the new plaintext api_key exactly once.
API key issued via POST /v1/{workspace_id}/api-keys. Pass the returned api_key value as a Bearer token.
Successful Response
Missing or invalid API key.
Insufficient permissions.
API key not found.
Validation Error
POST /v1/{workspace_id}/api-keys/{key_id}/rotate HTTP/1.1
Host: platform.api-us.concurrence.com
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 19
{
"duration_days": 1
}{
"key_id": "text",
"api_key": "text",
"name": "text",
"role": "text",
"permissions": [
"text"
],
"expires_at": "2026-01-01T00:00:00.000Z",
"created_by_entity_id": "123e4567-e89b-12d3-a456-426614174000",
"created_by_credential_id": "123e4567-e89b-12d3-a456-426614174000"
}The rotated key inherits the original key's name, role, and permissions. You can only rotate keys you created (or any key if you have the api_key.delete permission).
Usage Visibility
API key responses include a last_used_at timestamp showing when the key was last used to authenticate a request. Use this to identify stale keys before rotating or revoking them.
Current Key Info
Use this operation to inspect the current key's workspace, expiration, and remaining validity.
Return information about the currently authenticated API key, including expiration.
API key issued via POST /v1/{workspace_id}/api-keys. Pass the returned api_key value as a Bearer token.
Successful Response
Missing or invalid API key.
GET /v1/auth/me HTTP/1.1
Host: platform.api-us.concurrence.com
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"workspace_id": "123e4567-e89b-12d3-a456-426614174000",
"key_id": "text",
"name": "text",
"expires_at": "2026-01-01T00:00:00.000Z",
"expires_in_seconds": 1
}Device Code Flow (CLI & Desktop Apps)
For CLI tools, desktop apps, and other environments where the user authenticates in a browser, the Platform API supports the RFC 8628 Device Authorization Grant.
Flow Overview
Your app requests a device code from
POST /device/codeThe response includes a
verification_uri_complete- your app opens this in the user's browserThe user signs in (if needed) and clicks Authorize on the approval page
Your app polls
POST /tokenwithgrant_type=device_codeuntil the user approvesThe token endpoint returns a workspace-scoped JWT
Step-by-Step
1. Request a device code:
Response:
2. Direct the user to the verification URL. Open verification_uri_complete in a browser. The user sees the device code and can approve or deny the request.
3. Poll for the token:
400 authorization_pending
User hasn't approved yet
Keep polling
400 slow_down
Polling too fast
Increase interval by 5 seconds
200
Approved
Extract access_token and refresh_token
300 workspace_selection_required
User has multiple workspaces
See Multi-Workspace below
400 expired_token
Code expired (15-min TTL)
Start over
400 access_denied
User clicked Deny
Show error to user
Multi-Workspace Users
If the user belongs to multiple workspaces, the token exchange returns HTTP 300 with a workspace list instead of a JWT. Your app must:
Present the workspace list to the user
Exchange the
refresh_tokenfrom the 300 response for a workspace-scoped JWT:
Alternatively, pass workspace_id in the initial /token poll to pre-select a workspace.
Platform SDK (TypeScript)
The SDK provides loginWithDeviceCode() which handles the full flow - code issuance, browser open, polling, and workspace selection:
Use TokenManager with FileTokenStorage to persist credentials across CLI sessions (stored at ~/.amigo/credentials.json).
Security
Device codes expire after 15 minutes
User codes are 8 characters (XXXX-YYYY format), designed to be easy to verify visually
Rate-limited by IP address on the
/device/codeendpointThe approval page requires the user to be signed in to the Console
Security Controls
The Platform API layers several account-level security controls on top of key and token authentication: SSO, multi-factor authentication, IP allowlists, account lockout, and session enforcement. The subsections below cover each control.
SSO Login
The Platform API supports single sign-on (SSO) via identity federation. Users authenticate with their organization's identity provider (e.g., Google Workspace), and the platform exchanges the provider's authorization code for a Concurrence JWT. That is the same token format used for API key authentication.
SSO Exchange Flow
The client application redirects the user to the identity provider for authentication
After authentication, the identity provider returns an authorization code
The client sends the authorization code to the Platform API token endpoint
The platform exchanges the code with the identity provider server-to-server and issues a JWT
Multi-Workspace Selection
When a user has credentials on multiple workspaces, the token endpoint returns a workspace list instead of a JWT. The client re-calls the token endpoint with the selected workspace_id to complete authentication.
Auto-Provisioning
Organizations can configure provision policies that automatically create credentials for new users on first SSO login based on their email domain. Global provision policies expand access to all workspaces in the organization. This eliminates the need to manually create credentials for each user before they can log in.
Refresh Tokens
SSO sessions issue refresh tokens alongside the JWT. Refresh tokens are rotated on each use (the old token is invalidated when a new one is issued). Idle sessions expire after the configured timeout to meet compliance requirements.
Multi-Factor Authentication (MFA)
The platform supports TOTP-based multi-factor authentication for user accounts.
Enrollment Flow
POST /mfa/enroll: generate a TOTP secret. Returns anotpauth://URI for scanning with an authenticator app, plus one-time recovery codes.POST /mfa/verify: confirm enrollment by submitting a valid TOTP code from the authenticator app.
Once enrolled, the token endpoint requires a valid TOTP code (or recovery code) alongside credentials when MFA is enforced.
MFA Enforcement
MFA can be enforced per SSO connection. When enforce_mfa is enabled on a connection, users authenticating through that identity provider must complete MFA before receiving a token.
Service accounts (client_credentials with service_account principal type) are exempt from MFA enforcement since automated services cannot interact with an authenticator app.
Recovery Codes
Recovery codes are generated during enrollment for account recovery if the authenticator device is lost. Each code can only be used once. Recovery codes are stored securely with one-way hashing.
Admin MFA Management
GET /admin/mfa/coverage
MFA enrollment statistics across the workspace
POST /admin/mfa/reset/{entity_id}
Reset MFA enrollment for a specific user (requires re-enrollment)
Requires identity:admin scope.
IP Allowlists
Per-workspace IP allowlists restrict which IP addresses can authenticate. When configured, only requests from allowed CIDR ranges are accepted on the token endpoint.
Allowlist Behavior
Allowlists are configured as CIDR ranges (e.g.,
10.0.0.0/8,203.0.113.45/32).When an allowlist is active, authentication requests from non-matching IPs are rejected with
403.Allowlist changes may take a few minutes to propagate.
Service accounts are exempt from IP allowlist checks (required for automated service-to-service auth).
IP checks fail open: if an allowlist cannot be evaluated, authentication proceeds rather than locking out all access.
Admin IP Allowlist Management
POST /admin/ip-allowlists
Add a CIDR range to the workspace allowlist
GET /admin/ip-allowlists
List all allowed CIDR ranges
DELETE /admin/ip-allowlists/{id}
Remove a CIDR range
POST /admin/ip-allowlists/test
Test whether a specific IP address matches the current allowlist
Requires identity:admin scope.
Account Lockout & Brute Force Protection
The platform enforces progressive account lockout on failed authentication attempts:
5+
5 minutes
10+
30 minutes
20+
Permanent (requires admin unlock)
Lockout is tracked per entity (API key or client) and per IP address. Successful authentication clears the lockout counter.
Per-IP rate limiting applies to unauthenticated grant types (api_key, client_credentials, personal_access_token, google_oauth, device_code, email_otp, magic_link) at 60 requests/minute on the /token endpoint. Already-authenticated grants (agent_session, refresh_token) are exempt.
Lockout protection fails open: if lockout tracking is temporarily unavailable, authentication proceeds normally.
Admin lockout management (requires identity:admin scope):
POST /admin/lockout/unlock/{identifier}
Unlock a locked account
GET /admin/lockout/locked-accounts
List all currently locked accounts
GET /admin/lockout/status/{identifier}
Check lockout status for an identifier
Locked responses return 403 with a Retry-After header for timed lockouts.
Session Enforcement
Sessions are enforced with idle timeouts and concurrent session limits. Idle and expired sessions are revoked automatically.
Idle Timeout
Sessions are revoked after a configurable period of inactivity:
Console user sessions
15 minutes
Default idle timeout
Agent sessions (voice calls)
1 hour
Matches agent session TTL; voice agents don't refresh mid-call
Configurable per-session between 5 minutes and 24 hours. Activity is tracked on each authenticated request.
Concurrent Session Limits
Each entity is limited to a maximum number of active sessions per workspace:
User sessions
5
Oldest session evicted when limit reached
Agent sessions
50
Oldest session evicted when limit reached
Agent sessions have a higher limit because a single service entity (e.g., a voice agent) handles many simultaneous calls.
Session Metadata
Sessions track IP address and user agent for audit and security visibility.
Session Management Endpoints
User endpoints (any authenticated user):
GET /sessions
List active sessions for the current entity
DELETE /sessions
Revoke all sessions for the current entity (logout everywhere)
DELETE /sessions/{session_id}
Revoke a specific session
Admin endpoints (requires identity:admin scope):
GET /sessions/admin
List all active sessions with optional entity/workspace filters, pagination
DELETE /sessions/admin/{entity_id}
Revoke all sessions for a specific entity (optional workspace scope)
All revocation actions are audit-logged.
Last updated
Was this helpful?

