Authentication
API authentication, token generation, and security best practices
Authentication
All Cliqer API requests require authentication via Bearer token.
Token Types
| Type | Use Case | Expiry | Scope |
|---|---|---|---|
| Access Token | API requests | 15 min | User-specific |
| Refresh Token | Token renewal | 7 days | Refresh only |
| API Key | Server-to-server | Configurable | Custom scopes |
Using Tokens
Include the token in the Authorization header:
curl -X GET "$BASE_URL/api/..." \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
const response = await fetch('$BASE_URL/api/...', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
})
import requests
headers = {'Authorization': f'Bearer {access_token}'}
response = requests.get('$BASE_URL/api/...', headers=headers)
req, _ := http.NewRequest("GET", "$BASE_URL/api/...", nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
resp, _ := http.DefaultClient.Do(req)
$ch = curl_init('$BASE_URL/api/...');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $accessToken
]);
$response = curl_exec($ch);
let client = reqwest::Client::new();
let response = client
.get("$BASE_URL/api/...")
.header("Authorization", format!("Bearer {}", access_token))
.send()
.await?;
Obtain Access Token
Exchange user credentials for tokens.
Endpoint: POST /api/auth/token
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Yes | User email |
password | string | Yes | User password |
curl -X POST "$BASE_URL/api/auth/token" \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"your_password"}'
interface TokenResponse {
access_token: string
refresh_token: string
expires_in: number
token_type: 'Bearer'
}
const tokens: TokenResponse = await $fetch('/api/auth/token', {
method: 'POST',
body: { email: 'user@example.com', password: 'your_password' }
})
import requests
data = {'email': 'user@example.com', 'password': 'your_password'}
response = requests.post('$BASE_URL/api/auth/token', json=data)
tokens = response.json()
payload := map[string]string{"email": "user@example.com", "password": "your_password"}
body, _ := json.Marshal(payload)
resp, _ := http.Post("$BASE_URL/api/auth/token", "application/json", bytes.NewBuffer(body))
$ch = curl_init('$BASE_URL/api/auth/token');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'email' => 'user@example.com',
'password' => 'your_password'
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
struct LoginRequest { email: String, password: String }
#[derive(Deserialize)]
struct TokenResponse {
access_token: String,
refresh_token: String,
expires_in: u64,
token_type: String,
}
let client = reqwest::Client::new();
let response = client
.post("$BASE_URL/api/auth/token")
.json(&LoginRequest { email: "user@example.com".into(), password: "your_password".into() })
.send()
.await?
.json::<TokenResponse>()
.await?;
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...",
"expires_in": 900,
"token_type": "Bearer"
}
Refresh Token
Obtain a new access token using a refresh token.
Endpoint: POST /api/auth/refresh
curl -X POST "$BASE_URL/api/auth/refresh" \
-H "Content-Type: application/json" \
-d '{"refresh_token":"your_refresh_token"}'
const tokens: TokenResponse = await $fetch('/api/auth/refresh', {
method: 'POST',
body: { refresh_token: refreshToken }
})
response = requests.post('$BASE_URL/api/auth/refresh',
json={'refresh_token': refresh_token})
payload := map[string]string{"refresh_token": refreshToken}
body, _ := json.Marshal(payload)
resp, _ := http.Post("$BASE_URL/api/auth/refresh", "application/json", bytes.NewBuffer(body))
$ch = curl_init('$BASE_URL/api/auth/refresh');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['refresh_token' => $refreshToken]));
$response = curl_exec($ch);
let response = client
.post("$BASE_URL/api/auth/refresh")
.json(&serde_json::json!({"refresh_token": refresh_token}))
.send()
.await?;
Revoke Token
Invalidate a token (logout).
Endpoint: POST /api/auth/revoke
curl -X POST "$BASE_URL/api/auth/revoke" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
await $fetch('/api/auth/revoke', {
method: 'POST',
headers: { Authorization: `Bearer ${accessToken}` }
})
requests.post('$BASE_URL/api/auth/revoke',
headers={'Authorization': f'Bearer {access_token}'})
req, _ := http.NewRequest("POST", "$BASE_URL/api/auth/revoke", nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
http.DefaultClient.Do(req)
$ch = curl_init('$BASE_URL/api/auth/revoke');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $accessToken]);
curl_exec($ch);
client.post("$BASE_URL/api/auth/revoke")
.header("Authorization", format!("Bearer {}", access_token))
.send()
.await?;
Admin: API Key Management
Admin Only - Requires
admin:api-keys permission.Generate API Key
Create a new API key for server-to-server integrations.
Endpoint: POST /api/admin/api-keys
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Key identifier (e.g., "Production Server") |
scopes | string | Yes | Permissions: read, write, admin, or specific resources |
expires_in | number | No | Expiry in seconds (default: never) |
ip_allowlist | string | No | Restrict to specific IPs/CIDRs |
rate_limit | number | No | Custom requests/minute (default: tier limit) |
curl -X POST "$BASE_URL/api/admin/api-keys" \
-H "Authorization: Bearer ADMIN_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Server",
"scopes": ["connections:read", "connections:write", "licenses:read"],
"expires_in": 31536000,
"ip_allowlist": ["203.0.113.0/24", "198.51.100.50"],
"rate_limit": 500
}'
interface ApiKey {
id: string
key: string // Only returned once at creation
name: string
scopes: string[]
created_at: string
expires_at: string | null
last_used_at: string | null
}
const apiKey: ApiKey = await $fetch('/api/admin/api-keys', {
method: 'POST',
headers: { Authorization: `Bearer ${adminToken}` },
body: {
name: 'Production Server',
scopes: ['connections:read', 'connections:write', 'licenses:read'],
expires_in: 31536000,
ip_allowlist: ['203.0.113.0/24', '198.51.100.50'],
rate_limit: 500
}
})
// IMPORTANT: Store apiKey.key securely - it won't be shown again
import requests
headers = {'Authorization': f'Bearer {admin_token}', 'Content-Type': 'application/json'}
data = {
'name': 'Production Server',
'scopes': ['connections:read', 'connections:write', 'licenses:read'],
'expires_in': 31536000,
'ip_allowlist': ['203.0.113.0/24', '198.51.100.50'],
'rate_limit': 500
}
response = requests.post('$BASE_URL/api/admin/api-keys', headers=headers, json=data)
api_key = response.json()
# IMPORTANT: Store api_key['key'] securely - it won't be shown again
type ApiKeyRequest struct {
Name string `json:"name"`
Scopes []string `json:"scopes"`
ExpiresIn int `json:"expires_in,omitempty"`
IpAllowlist []string `json:"ip_allowlist,omitempty"`
RateLimit int `json:"rate_limit,omitempty"`
}
payload := ApiKeyRequest{
Name: "Production Server",
Scopes: []string{"connections:read", "connections:write", "licenses:read"},
ExpiresIn: 31536000,
IpAllowlist: []string{"203.0.113.0/24", "198.51.100.50"},
RateLimit: 500,
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "$BASE_URL/api/admin/api-keys", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+adminToken)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
$ch = curl_init('$BASE_URL/api/admin/api-keys');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'name' => 'Production Server',
'scopes' => ['connections:read', 'connections:write', 'licenses:read'],
'expires_in' => 31536000,
'ip_allowlist' => ['203.0.113.0/24', '198.51.100.50'],
'rate_limit' => 500
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $adminToken,
'Content-Type: application/json'
]);
$response = curl_exec($ch);
// IMPORTANT: Store the key securely - it won't be shown again
#[derive(Serialize)]
struct ApiKeyRequest {
name: String,
scopes: Vec<String>,
expires_in: Option<u64>,
ip_allowlist: Option<Vec<String>>,
rate_limit: Option<u32>,
}
#[derive(Deserialize)]
struct ApiKey {
id: String,
key: String, // Only returned once at creation
name: String,
scopes: Vec<String>,
created_at: String,
expires_at: Option<String>,
}
let request = ApiKeyRequest {
name: "Production Server".into(),
scopes: vec!["connections:read".into(), "connections:write".into(), "licenses:read".into()],
expires_in: Some(31536000),
ip_allowlist: Some(vec!["203.0.113.0/24".into(), "198.51.100.50".into()]),
rate_limit: Some(500),
};
let api_key: ApiKey = client
.post("$BASE_URL/api/admin/api-keys")
.header("Authorization", format!("Bearer {}", admin_token))
.json(&request)
.send()
.await?
.json()
.await?;
// IMPORTANT: Store api_key.key securely - it won't be shown again
Response:
{
"id": "key_abc123",
"key": "clq_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890",
"name": "Production Server",
"scopes": ["connections:read", "connections:write", "licenses:read"],
"ip_allowlist": ["203.0.113.0/24", "198.51.100.50"],
"rate_limit": 500,
"created_at": "2025-01-15T10:30:00Z",
"expires_at": "2026-01-15T10:30:00Z",
"last_used_at": null
}
Security Warning - The
key value is only returned once at creation. Store it securely immediately. If lost, delete and create a new key.List API Keys
Endpoint: GET /api/admin/api-keys
curl -X GET "$BASE_URL/api/admin/api-keys" \
-H "Authorization: Bearer ADMIN_ACCESS_TOKEN"
interface ApiKeySummary {
id: string
name: string
scopes: string[]
created_at: string
expires_at: string | null
last_used_at: string | null
}
const keys: ApiKeySummary[] = await $fetch('/api/admin/api-keys', {
headers: { Authorization: `Bearer ${adminToken}` }
})
response = requests.get('$BASE_URL/api/admin/api-keys',
headers={'Authorization': f'Bearer {admin_token}'})
keys = response.json()
req, _ := http.NewRequest("GET", "$BASE_URL/api/admin/api-keys", nil)
req.Header.Set("Authorization", "Bearer "+adminToken)
resp, _ := http.DefaultClient.Do(req)
$ch = curl_init('$BASE_URL/api/admin/api-keys');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $adminToken]);
$response = curl_exec($ch);
let keys: Vec<ApiKeySummary> = client
.get("$BASE_URL/api/admin/api-keys")
.header("Authorization", format!("Bearer {}", admin_token))
.send()
.await?
.json()
.await?;
Revoke API Key
Endpoint: DELETE /api/admin/api-keys/{id}
curl -X DELETE "$BASE_URL/api/admin/api-keys/key_abc123" \
-H "Authorization: Bearer ADMIN_ACCESS_TOKEN"
await $fetch('/api/admin/api-keys/key_abc123', {
method: 'DELETE',
headers: { Authorization: `Bearer ${adminToken}` }
})
requests.delete('$BASE_URL/api/admin/api-keys/key_abc123',
headers={'Authorization': f'Bearer {admin_token}'})
req, _ := http.NewRequest("DELETE", "$BASE_URL/api/admin/api-keys/key_abc123", nil)
req.Header.Set("Authorization", "Bearer "+adminToken)
http.DefaultClient.Do(req)
$ch = curl_init('$BASE_URL/api/admin/api-keys/key_abc123');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $adminToken]);
curl_exec($ch);
client.delete("$BASE_URL/api/admin/api-keys/key_abc123")
.header("Authorization", format!("Bearer {}", admin_token))
.send()
.await?;
Available Scopes
| Scope | Description |
|---|---|
read | Read access to all resources |
write | Write access to all resources |
admin | Full administrative access |
connections:read | View active connections |
connections:write | Manage connections (kick, broadcast) |
licenses:read | View license information |
licenses:write | Manage licenses |
rooms:read | View room information |
rooms:write | Create/manage rooms |
users:read | View user information |
users:write | Manage users |
Security Best Practices
Recommended Security Measures
- Use short-lived access tokens - Default 15 minutes, use refresh tokens for renewal
- Restrict IP allowlists - Limit API keys to known server IPs
- Minimum scopes - Only grant permissions actually needed
- Rotate keys regularly - Set expiry and rotate before expiration
- Monitor usage - Check
last_used_atand audit logs for anomalies - Secure storage - Store tokens in environment variables, never in code
- Use HTTPS only - All API requests must use TLS 1.2+
Token Storage Recommendations
| Environment | Storage Method |
|---|---|
| Browser | HttpOnly cookies or secure memory |
| Server | Environment variables |
| Mobile | Secure keychain/keystore |
| CI/CD | Secret management (Vault, AWS Secrets) |
See Security Overview for enterprise security features.