Zeuslock REST API Reference
Authenticate, list incidents, update policies and run synchronous scans against the Zeuslock REST API. Includes curl, Python and Node.js examples.
Base URL and hosting
All Zeuslock REST endpoints live under a single base URL:
https://api.zeuslock.ai/v1The shared multi-tenant API is hosted in eu-west-3 (Paris). Customer data, incident records and configuration never leave the EU. If you run the sovereign edition, your base URL is your own dedicated subdomain — typically https://api.<tenant>.zeuslock.cloud/v1 — with the same endpoints, payloads and behaviour as the shared API. Swap the host, leave everything else identical.
The API speaks JSON over HTTPS only. Plain HTTP requests are rejected at the edge. Every connection terminates on TLS 1.3 with Perfect Forward Secrecy.
Authentication
Two authentication methods are supported. Pick one per integration; do not mix them in the same request.
API key (server-to-server)
API keys are the default for backend automation, SIEM integrations, CI/CD pipelines and scripts. Create one in the Operator Console under Settings → API Keys, then pass it as a bearer token:
curl -H "Authorization: Bearer zlsk_live_8f3c..." \
https://api.zeuslock.ai/v1/incidentsKeys are prefixed zlsk_live_ or zlsk_test_ so you can tell environments apart at a glance. Each key has an explicit scope:
read-only— GET on incidents, policies, detectors.incidents-write— can update incident status and post comments.admin— can create or modify detectors, policies and webhooks.
Keys are shown once at creation. Rotate them through the console; revocation takes effect within five seconds across all regions.
OAuth 2.0 (user-on-behalf)
For UI integrations that act on behalf of a logged-in operator, use the standard authorization code flow with PKCE. The authorization endpoint is https://app.zeuslock.ai/oauth/authorize and the token endpoint is https://api.zeuslock.ai/v1/oauth/token. Scopes mirror the API key scopes. Access tokens are short-lived (one hour) and refresh tokens rotate on every use.
Rate limits
Each API key is allowed 60 requests per second sustained and a 600 req/s burst. The bucket refills at the sustained rate. Every response carries three headers so your client can self-regulate:
X-RateLimit-Limit— the sustained ceiling for this key.X-RateLimit-Remaining— requests left in the current window.X-RateLimit-Reset— epoch seconds at which the window resets.
When the bucket is empty the API returns 429 Too Many Requests with a Retry-After header (seconds). Back off exponentially; do not retry tighter than the value returned.
Endpoints at a glance
| Method | Path | Purpose | Scope |
|---|---|---|---|
| GET | /v1/incidents | List incidents (cursor paginated) | read-only |
| GET | /v1/incidents/{id} | Fetch a full incident with redacted prompt context | read-only |
| POST | /v1/incidents/{id}/status | Update incident status and add a comment | incidents-write |
| GET | /v1/policies | List active policies | read-only |
| PUT | /v1/policies/{id} | Update a policy (supports dry-run) | admin |
| GET | /v1/detectors | List custom detectors | read-only |
| POST | /v1/detectors | Create a custom detector | admin |
| POST | /v1/scan | Synchronous DLP scan of arbitrary text | read-only |
Incidents
List incidents
Lists incidents in reverse chronological order with cursor-based pagination. The next_cursor field, when present, is opaque — pass it back verbatim in the next call.
curl -H "Authorization: Bearer zlsk_live_8f3c..." \
"https://api.zeuslock.ai/v1/incidents?since=2026-05-01T00:00:00Z&severity=high&limit=100"import httpx
client = httpx.Client(
base_url="https://api.zeuslock.ai/v1",
headers={"Authorization": "Bearer zlsk_live_8f3c..."},
timeout=30,
)
params = {"since": "2026-05-01T00:00:00Z", "severity": "high", "limit": 100}
while True:
resp = client.get("/incidents", params=params)
resp.raise_for_status()
page = resp.json()
for incident in page["items"]:
print(incident["id"], incident["detector"], incident["severity"])
if not page.get("next_cursor"):
break
params["cursor"] = page["next_cursor"]import fetch from "node-fetch";
const base = "https://api.zeuslock.ai/v1";
const headers = { Authorization: "Bearer zlsk_live_8f3c..." };
let cursor;
do {
const url = new URL(`${base}/incidents`);
url.searchParams.set("since", "2026-05-01T00:00:00Z");
url.searchParams.set("severity", "high");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, { headers });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const page = await res.json();
for (const i of page.items) console.log(i.id, i.detector, i.severity);
cursor = page.next_cursor;
} while (cursor);Fetch a single incident
GET /v1/incidents/{id} returns the full incident, including the prompt context. Sensitive matches are returned redacted by default (for example sk-***), with offsets so you can reconstruct the visualisation in your tooling.
Update incident status
Move an incident through your triage workflow. Valid values are open, in_review, resolved and false_positive. A false_positive transition feeds back into the detector calibration job that runs nightly.
curl -X POST "https://api.zeuslock.ai/v1/incidents/inc_8c1f.../status" \
-H "Authorization: Bearer zlsk_live_8f3c..." \
-H "Content-Type: application/json" \
-d '{"status":"resolved","comment":"Confirmed test key, rotated."}'Policies
GET /v1/policies lists every active policy, including the mode (monitor, anonymize or block) per finding type and the user groups it applies to.
PUT /v1/policies/{id} updates a policy. Append ?dry_run=true to validate the change against the last seven days of traffic without actually applying it — the response contains the diff of incidents that would have been created, anonymized or blocked. This is the recommended way to graduate from Monitor to Block.
resp = client.put(
"/policies/pol_credentials",
params={"dry_run": "true"},
json={"findings": {"api_key": {"mode": "block"}}},
)
resp.raise_for_status()
print(resp.json()["dry_run_summary"]) # {"would_block": 12, "would_warn": 0, ...}Detectors
Custom detectors capture org-specific identifiers — internal employee IDs, customer reference numbers, proprietary token formats. GET /v1/detectors lists them; POST /v1/detectors creates one. Each detector combines a regex with one or more post-validators to keep precision high:
luhn— card-number style checksum.mod97— IBAN-style checksum.entropy_gt— reject matches whose Shannon entropy is below a threshold (typically4.5) to avoid placeholder strings.
const body = {
name: "internal_customer_id",
regex: "\\bCUST-[A-Z0-9]{10}\\b",
post_validators: [{ type: "entropy_gt", value: 4.5 }],
severity: "medium",
action: "anonymize"
};
const res = await fetch(`${base}/detectors`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify(body)
});
console.log(await res.json());Synchronous scan
POST /v1/scan is the workhorse for CI/CD jobs and batch tasks. Send raw text, get findings back in a single round trip. Requests are capped at 1 MB per call with a 30-second timeout; for larger payloads, split client-side or use the asynchronous batch endpoint (covered in a separate doc).
curl -X POST "https://api.zeuslock.ai/v1/scan" \
-H "Authorization: Bearer zlsk_live_8f3c..." \
-H "Content-Type: application/json" \
-d '{"text":"Connect with AKIAIOSFODNN7EXAMPLE and password hunter2","detectors":["api_key","password"]}'payload = {"text": open("build.log").read(), "detectors": ["api_key", "private_key", "jwt"]}
findings = client.post("/scan", json=payload).json()["findings"]
if findings:
raise SystemExit(f"Build failed: {len(findings)} secrets detected")Errors
Every error response uses the same JSON envelope so you can log and route them uniformly:
{
"error": {
"code": "invalid_scope",
"message": "This API key is read-only and cannot modify policies.",
"request_id": "req_01HX9YJ3K4M5N6P7Q8R9S0T1U2"
}
}Always quote request_id when contacting support. Common HTTP statuses:
400Bad Request — malformed JSON or missing required field.401Unauthorized — missing or invalid bearer token.403Forbidden — valid token but wrong scope.404Not Found — unknown resource or no access in this tenant.422Unprocessable Entity — request shape is valid but values fail validation (bad regex, invalid status transition).429Too Many Requests — honourRetry-After.500Internal Server Error — safe to retry with exponential backoff.
Webhooks
For push-based delivery of incidents to Slack, Splunk HEC, PagerDuty or Microsoft Sentinel, configure a webhook in the Operator Console. See the Webhooks doc for the payload schema and HMAC signature verification.
Pin the API version with a header (X-Zeuslock-API-Version: 2026-05-01) once it becomes available in your tenant. It freezes response shapes for that integration so future additive changes never break your parser.