Skip to content

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):

{ "name": "Production App" }

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)
curl -u admin:password https://face-api.xylolabs.com/api/v1/admin/api-keys
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)
curl -u admin:password https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...
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)
{ "name": "New Name", "is_active": false }

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)
curl -u admin:password -X DELETE \
  https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...
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.