GET /api/v1/systems
The organisation’s inventory of AI systems.
Filters: status
Fields: id, name, provider_name, status, is_agentic, created_at, updated_at
Available on the M2 and M3 plans and during the trial. The organisation Owner creates and revokes the key from Settings → Integrations → API.
Every request carries the organisation key in the Authorization header, as a Bearer token.
Authorization: Bearer alx_live_…
The key starts with alx_live_. Its first 17 characters are the prefix shown in Settings → Integrations → API. The full key is shown only once, when it is created: Alethexis stores only its SHA-256 fingerprint and cannot show it to you again.
A revoked key stops working on the next request.
A missing, unknown, wrong or revoked key gets the same 401 response: the API does not tell these cases apart.
Each key allows 120 requests per minute and 10,000 per day.
Every authenticated response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. When the quota runs out, the response is 429 with Retry-After, in seconds.
In addition, each IP address allows 60 requests every 10 seconds. Above that, requests are blocked for 10 seconds.
List responses return an object with data and next_cursor.
limit ranges from 1 to 200; the default is 50.
For the next page, repeat the request with cursor set to the next_cursor you received. On the last page, next_cursor is null.
The cursor is opaque: do not build or modify it.
All errors share one shape: an error object with code and message. The message is technical English; the code is stable.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_cursor | The cursor is not valid for that resource. |
| 400 | invalid_parameter | A parameter does not exist, is repeated or has a value that is not accepted. |
| 401 | invalid_api_key | Missing, unknown, wrong or revoked key. |
| 403 | module_required | The organisation has neither M2 nor M3 active. |
| 404 | not_found | The resource does not exist. |
| 429 | rate_limited | The key’s quota is exhausted. |
| 500 | internal | Internal error. |
All of them accept limit, cursor and since (an ISO 8601 date with time zone).
The organisation’s inventory of AI systems.
Filters: status
Fields: id, name, provider_name, status, is_agentic, created_at, updated_at
Risk classifications for each system, with their history.
Filters: system_id, current
Fields: id, system_id, level, is_current, classified_at
Assigned controls and their status, per system or for the whole organisation.
Filters: system_id, status
Fields: control_id, code, title, module, kind, system_id, status, updated_at
Evidence index: category, size and date. The file itself is not included.
Filters: system_id
Fields: id, section_id, category, size_bytes, uploaded_at
Status of each section of the platform.
Filters: status
Fields: section_id, module, status, updated_at
Submission, review, approval, rejection, replacement and declaration acts, with their fingerprint.
Filters: subject_type
Fields: id, subject_type, subject_id, subject_version, action, decided_at, content_hash, statement_version
The organisation’s audit log, with the chained fingerprint of each row.
Filters: action
Fields: id, action, family, table_name, record_id, occurred_at, source, row_hash
AI use requests that have been submitted. Drafts are not included.
Filters: status
Fields: id, status, tool_name, provider_name, urgency, involves_personal_data, submitted_at, decided_at, system_id
By design, the API does not include fields intended for personal data (authors, emails, IP addresses, changed values) or hand-written text. System and tool names are set by your organisation: do not put personal data in them. Per resource, it leaves out:
systems: who created or edited the system, the person named in the prohibited-practices check, the purpose and the notes.classifications: who classified it and the written justification.controls: the assigned owner and approver, the notes and the links to evidence files.evidence: the file, its storage path, the title, the description and who uploaded it.sections: the review reason and its details.approvals: who recorded the act, their roles and the comment.audit-log: who acted, the IP address, the browser and the values before and after the change.requests: who raised, reviewed or decided it, the purpose, the intended users, the notes on personal data, the decision comment and the comments.A webhook notifies your system as soon as something happens, without polling the API. The Owner registers them in Settings → Integrations → Webhooks: an https URL, the events you want and a secret shown only once.
Every delivery is a POST with this body:
{
"id": "0f3a…", // Alethexis-Event-Id
"type": "section.closed",
"created_at": "2026-09-18T09:12:03.412Z",
"api_version": "v1",
"data": { "account_id": "…", "section_id": "GOB-2", "status": "complete", "closed_at": "…" }
}Headers: Alethexis-Signature, Alethexis-Event-Id, Alethexis-Event-Type and User-Agent: Alethexis-Webhooks/1.
The v1 value is the HMAC-SHA256 of the timestamp, a dot and the exact body you receive, using the endpoint secret. Compare in constant time and reject anything more than five minutes old.
Verification:
// Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(rawBody, header, secret, toleranceSeconds = 300) {
const [t, v1] = header.split(',').map((part) => part.split('=')[1]);
if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSeconds) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
const received = Buffer.from(v1, 'hex');
return expected.length === received.length && timingSafeEqual(expected, received);
}# Python
import hashlib, hmac, time
def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > tolerance:
return False
expected = hmac.new(
secret.encode(), f'{parts["t"]}.'.encode() + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, parts["v1"])A 2xx response is a good delivery; anything else, or a 10-second timeout, is a failure. The first attempt is made straight away; if it fails, it is retried one hour after the failure, and then two, four and eight hours after the previous failure. That makes five attempts in total: after the fifth failure the delivery is exhausted and you can replay it by hand from Settings → Integrations → Webhooks. Exhausted deliveries appear in Alethexis’s internal daily report.
Alethexis-Event-Id identifies the event, not the attempt: a retry repeats the same identifier. Keep the ones you have processed and discard repeats. The same event arrives once per subscribed endpoint.
v1 has nine events. Each endpoint receives only the events it subscribes to.
| Event | When it is emitted | data fields |
|---|---|---|
section.closed | When a section is closed. | section_id, status, closed_at |
section.reopened | When a section is reopened or moves to review. | section_id, status, reason_key, reopened_at |
system.created | When an AI system is added. | system_id, created_at |
system.reclassified | When a new classification is recorded for a system. | system_id, classification_from, classification_to, version, classified_at |
risk.high_registered | When a high inherent risk (15 or above) is recorded, or a risk crosses that threshold. | risk_id, system_id, risk_category, inherent_score, risk_level, registered_at |
approval.registered | When an approval is recorded. | approval_id, subject_type, subject_id, subject_version, content_hash, decided_at |
evidence.uploaded | When evidence is uploaded or a PDF is registered as evidence. | evidence_id, section_id, system_id, category, size_bytes, uploaded_at |
request.decided | When an AI use request is decided. | request_id, status, system_id, decided_at |
product.corrective_action | When a corrective action under Article 20 is recorded for a version of an own product. | corrective_action_id, system_id, version_id, measure, taken_on |
Payloads carry identifiers, states and dates. Never names, free text or personal data. The detail is read afterwards with the API. Until 18 September 2026 some events also carried the name of the system or tool; they no longer do, because that name is written by your organisation and nothing stops it from containing a person’s.
Every payload carries the identifiers you need to ask the API for the rest with your key. A `system_id` gives you its classification, its controls and its evidence:
# data.system_id from a system.created / system.reclassified payload SYSTEM_ID="8f2c…" curl -H "Authorization: Bearer $ALETHEXIS_API_KEY" \ "https://www.alethexis.com/api/v1/classifications?system_id=$SYSTEM_ID¤t=true" curl -H "Authorization: Bearer $ALETHEXIS_API_KEY" \ "https://www.alethexis.com/api/v1/controls?system_id=$SYSTEM_ID" curl -H "Authorization: Bearer $ALETHEXIS_API_KEY" \ "https://www.alethexis.com/api/v1/evidence?system_id=$SYSTEM_ID"
Risks do not have their own API resource yet: from `risk.high_registered` you can resolve the system through its `system_id`, and the risk itself is consulted in the platform.
The URL must be https and resolve to a public address: webhooks are not delivered to private or internal addresses. Respond within 10 seconds; if you need longer, accept the delivery and process it afterwards.
v1 is a contract: its responses do not change in incompatible ways. An incompatible change ships as a new version, v2.
The OpenAPI 3.1 specification is generated from the same schemas that validate every response: /api/v1/openapi.json
curl -H "Authorization: Bearer $ALETHEXIS_API_KEY" \ "https://www.alethexis.com/api/v1/systems?limit=50"