API Key Management¶
Admin endpoints for creating and managing API keys.
- Browser admin console uses the cookie-backed admin session
- Direct admin API clients can use HTTP Basic Auth
- All /api/v1/admin/api-keys* endpoints require the database-backed mode and return 501 when the database tier is disabled
Browser admin session endpoints:
- POST /api/v1/admin/auth/login
- GET /api/v1/admin/auth/session
- POST /api/v1/admin/auth/logout
Keys are stored as hashes at rest. The full raw key is returned only once at creation time; subsequent list/get calls return display metadata only.
POST /api/v1/admin/api-keys¶
Creates a new API key. Requires the database-backed mode; returns 501 when the database tier is disabled.
- Request format: JSON
- Response format: JSON (
ApiKeyCreatedResponse)
Request Body (JSON):
Response (201):
{
"id": "a1b2c3d4-e5f6-...",
"key": "xyl_7f3a8b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e",
"name": "Production App",
"key_prefix": "xyl_7f3a8b2",
"key_suffix": "9b0c1d2e",
"key_preview": "xyl_7f3a8b2...9b0c1d2e",
"is_active": true,
"request_count": 0,
"last_used_at": null,
"created_at": "2026-04-06T10:30:00+00:00"
}
Example¶
curl -u admin:password -X POST \
https://face-api.xylolabs.com/api/v1/admin/api-keys \
-H "Content-Type: application/json" \
-d '{"name": "Production App"}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.post(
"https://face-api.xylolabs.com/api/v1/admin/api-keys",
auth=HTTPBasicAuth("admin", "password"),
json={"name": "Production App"},
)
print(response.json())
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/api-keys", {
method: "POST",
headers: {
Authorization: `Basic ${basic}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ name: "Production App" }),
});
console.log(await response.json());
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
using var content = new StringContent("{\"name\":\"Production App\"}", Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://face-api.xylolabs.com/api/v1/admin/api-keys", content);
Console.WriteLine(await response.Content.ReadAsStringAsync());
GET /api/v1/admin/api-keys¶
Returns the full key list with usage metrics. Returns metadata only — not the full raw key. Requires the database-backed mode; returns 501 when the database tier is disabled.
- Request format: none (query string only)
- Response format: JSON (
PaginatedResponse[ApiKeyResponse])
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
int | 1 | Page number |
per_page |
int | 20 | Items per page (1-100) |
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
"https://face-api.xylolabs.com/api/v1/admin/api-keys",
auth=HTTPBasicAuth("admin", "password"),
)
print(response.json())
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/api-keys", {
headers: { Authorization: `Basic ${basic}` },
});
console.log(await response.json());
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
var response = await client.GetAsync("https://face-api.xylolabs.com/api/v1/admin/api-keys");
Console.WriteLine(await response.Content.ReadAsStringAsync());
{
"items": [
{
"id": "a1b2c3d4-e5f6-...",
"name": "Production App",
"key_prefix": "xyl_7f3a8b2",
"key_suffix": "9b0c1d2e",
"key_preview": "xyl_7f3a8b2...9b0c1d2e",
"is_active": true,
"request_count": 142,
"last_used_at": "2026-04-12T10:30:00+00:00",
"created_at": "2026-04-06T10:30:00+00:00"
}
],
"total": 1,
"page": 1,
"per_page": 20,
"pages": 1
}
GET /api/v1/admin/api-keys/{key_id}¶
Returns a specific key with usage stats. Returns metadata only — not the full raw key. Requires the database-backed mode; returns 501 when the database tier is disabled.
- Request format: none
- Response format: JSON (
ApiKeyResponse)
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
"https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...",
auth=HTTPBasicAuth("admin", "password"),
)
print(response.json())
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...", {
headers: { Authorization: `Basic ${basic}` },
});
console.log(await response.json());
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
var response = await client.GetAsync("https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...");
Console.WriteLine(await response.Content.ReadAsStringAsync());
{
"id": "a1b2c3d4-e5f6-...",
"name": "Production App",
"key_prefix": "xyl_7f3a8b2",
"key_suffix": "9b0c1d2e",
"key_preview": "xyl_7f3a8b2...9b0c1d2e",
"is_active": true,
"request_count": 142,
"last_used_at": "2026-04-12T10:30:00+00:00",
"created_at": "2026-04-06T10:30:00+00:00"
}
PATCH /api/v1/admin/api-keys/{key_id}¶
Updates a key name or activation state. Requires the database-backed mode; returns 501 when the database tier is disabled.
- Request format: JSON
- Response format: JSON (
ApiKeyResponse)
Revoke a key¶
curl -u admin:password -X PATCH \
https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-... \
-H "Content-Type: application/json" \
-d '{"is_active": false}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.patch(
"https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...",
auth=HTTPBasicAuth("admin", "password"),
json={"is_active": False},
)
print(response.json())
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...", {
method: "PATCH",
headers: {
Authorization: `Basic ${basic}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ is_active: false }),
});
console.log(await response.json());
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
using var request = new HttpRequestMessage(new HttpMethod("PATCH"), "https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...")
{
Content = new StringContent("{\"is_active\":false}", Encoding.UTF8, "application/json"),
};
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());
DELETE /api/v1/admin/api-keys/{key_id}¶
Deletes a key permanently. Requires the database-backed mode; returns 501 when the database tier is disabled. Returns 204 on success.
- Request format: none
- Response format: empty body (
204 No Content)
import requests
from requests.auth import HTTPBasicAuth
response = requests.delete(
"https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...",
auth=HTTPBasicAuth("admin", "password"),
)
print(response.status_code)
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...", {
method: "DELETE",
headers: { Authorization: `Basic ${basic}` },
});
console.log(response.status);
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
var response = await client.DeleteAsync("https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...");
Console.WriteLine(response.StatusCode);
Per-key metrics¶
Each key tracks:
| Field | Description |
|---|---|
key_prefix |
Stable leading portion for identification |
key_suffix |
Stable trailing portion for identification |
key_preview |
Display-friendly preview built from prefix + suffix |
request_count |
Total API calls made with this key |
last_used_at |
Timestamp of most recent use |
is_active |
Whether the key is accepted or revoked |
Metrics update asynchronously after each request.
Using a key¶
Pass the key in the X-API-Key header:
- Request format:
multipart/form-data - Response format: JSON (
DetectResponse)
curl -X POST https://face-api.xylolabs.com/api/v1/detect \
-H "X-API-Key: xyl_your_key_here" \
-F "image=@photo.jpg"
import requests
response = requests.post(
"https://face-api.xylolabs.com/api/v1/detect",
headers={"X-API-Key": "xyl_your_key_here"},
files={"image": open("photo.jpg", "rb")},
)
print(response.json())
const formData = new FormData();
formData.append("image", fileInput.files[0]);
const response = await fetch("https://face-api.xylolabs.com/api/v1/detect", {
method: "POST",
headers: { "X-API-Key": "xyl_your_key_here" },
body: formData,
});
console.log(await response.json());
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "xyl_your_key_here");
using var content = new MultipartFormDataContent();
content.Add(new ByteArrayContent(File.ReadAllBytes("photo.jpg")), "image", "photo.jpg");
var response = await client.PostAsync("https://face-api.xylolabs.com/api/v1/detect", content);
Console.WriteLine(await response.Content.ReadAsStringAsync());
Revoked keys return 403. Missing or invalid keys return 401.