Cyresora API, as shipped in the download
Cyresora API
Every running deadline over HTTP, plus an MCP endpoint for agents. The machine-readable spec lives at /api/openapi.json.
Authentication
Mint a key in Settings -> API & agents. Every request carries it as a bearer token:
curl https://cyresora.example.com/api/deadlines \
-H "Authorization: Bearer apk_xxxx"
Keys are scoped. Read-only or full, chosen when you mint it. A read-only key is refused any non-GET at the door, once, rather than in each route.
The one rule of this API
You cannot set a deadline. There is no due field anywhere, deliberately. You record two facts — when you became aware and when you actually submitted — and the instrument decides the rest. The one-month final-report clocks run from the submission instant, and the CRA's 14-day final report does not start at all until a corrective measure exists.
All times are UTC to the second. A 24-hour deadline cannot survive a timezone left implicit.
Endpoints
GET /api/ping | Verify the key | |||
GET /api/deadlines | Every running deadline, worst first, hours remaining as a number | |||
GET /api/incidents | The register with every regime ladder. `?state=overdue\ | due_soon\ | open\ | pending` |
POST /api/incidents | Open an incident; the clocks start from aware_at_utc | |||
GET /api/incidents/{id} | One incident with its ladders and everything sent | |||
POST /api/incidents/{id}/notifications | Record a notification you have sent | |||
GET /api/vulnerabilities | The vulnerability register, actively exploited first | |||
GET /api/openapi.json | The spec |
The endpoint a monitoring system polls
curl https://cyresora.example.com/api/deadlines -H "Authorization: Bearer apk_xxxx"
{
"now_utc": "2026-09-14 18:00:00",
"deadlines": [
{ "reference": "INC-20260914-A1B2", "regime": "cra_incident",
"stage": "early", "label": "Early warning", "provision": "Article 14(3)",
"due_utc": "2026-09-15 08:00:00", "hours_left": 14, "state": "open",
"counted_from": "becoming aware", "deadline_at_weekend": false }
]
}
hours_left is signed: negative means the deadline has passed. state is one of overdue, due_soon, open or pending — and pending means the clock has not started, with counted_from naming the event that will start it.
Open an incident
curl -X POST https://cyresora.example.com/api/incidents \
-H "Authorization: Bearer apk_xxxx" -H "Content-Type: application/json" \
-d '{"title":"Exploited auth bypass in the gateway",
"aware_at_utc":"2026-09-14 08:00:00",
"regimes":["cra_incident","nis2"],
"severity":"critical","malicious_suspected":true,"cross_border":true}'
A date with no time is refused — a 24-hour deadline cannot be computed from one. An incident with no regime is refused, because it would have no clock.
Record a notification
curl -X POST https://cyresora.example.com/api/incidents/1/notifications \
-H "Authorization: Bearer apk_xxxx" -H "Content-Type: application/json" \
-d '{"regime":"nis2","stage":"notify","submitted_at_utc":"2026-09-16 00:00:00",
"recipient":"BSI","channel":"Portal","reference":"BSI-INC-88213"}'
Needs notify.submit. Refused with 422 if the stage is unknown, the regime is not running on that incident, or the time is before awareness or in the future; 409 if that stage is already recorded — a duplicate is not a second duty discharged.
Webhooks
incident.opened, notification.recorded and deadline.alert, HMAC-signed with X-Cyresora-Signature: sha256=... over the raw body. notification.recorded fires however the filing was recorded — browser, REST or agent. deadline.alert fires at each of the owner's alert points before a reporting deadline, and once when it passes, with incident, regime, stage, due_utc, hours_left, alert_point_hours and overdue.
Connect it to an assistant
Mint the key in the app first: Settings → API & agents. Choose Read only when the assistant should answer questions but never change anything — the endpoint then lists only the read tools and refuses the rest by name, so a careless prompt cannot write. Full access behaves as before.
Every client needs the same three facts, and nothing in the handshake is vendor-specific:
| The address | https://cyresora.example.com/mcp |
| The key | header Authorization: Bearer apk_xxxx |
| The transport | MCP over streamable HTTP, stateless |
Claude — one command, or the same URL and header as a custom connector in the desktop and web apps:
claude mcp add --transport http cyresora https://cyresora.example.com/mcp \
--header "Authorization: Bearer apk_xxxx"
ChatGPT and the OpenAI API — one entry in the Responses API's tools array (in ChatGPT itself, the same URL and key go in as a connector):
{
"type": "mcp",
"server_label": "cyresora",
"server_url": "https://cyresora.example.com/mcp",
"authorization": "apk_xxxx",
"require_approval": "never"
}
Own Your AI reads a list of servers in this shape:
{
"mcpServers": [
{ "id": "cyresora", "name": "Cyresora", "url": "https://cyresora.example.com/mcp",
"token": "apk_xxxx", "enabled": true }
]
}
**Every other client spells the same three facts differently — copy the shape from its own documentation, not from here.** VS Code is the clearest example of why: its configuration reference (read 6 September 2026) puts servers in .vscode/mcp.json under a "servers" object — *"an object that maps server names to their configurations"* — not an mcpServers array. Pasted as-is, the block above will not load there. The id, the URL and the token are what travel; the JSON around them belongs to whichever client you are configuring.
A local model, n8n, or your own code — n8n's MCP Client node takes the URL and the same Authorization: Bearer header; a model running on your own machine reaches it through any MCP client, so nothing leaves your network at all. Writing it yourself is one POST of JSON-RPC 2.0:
curl -X POST https://cyresora.example.com/mcp \
-H "Authorization: Bearer apk_xxxx" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Any client that speaks MCP's streamable-HTTP transport works, including ones that do not exist yet: Cyresora implements the protocol, not an integration with a particular vendor.
Tools
| tool | access | what it does |
|---|---|---|
deadlines | read | Every running clock, worst first, with hours remaining and what each is counted from. Start here. |
list_incidents | read | The register with every regime ladder |
get_incident | read | One incident in full, with everything recorded against it |
what_applies | read | Each regime's stages, limits, anchors and application date — so an agent explains the duty without guessing |
open_incident | write | Open an incident; the clocks start from the awareness time you give |
record_notification | write | Record a notification already sent |
Read tools are declared readOnly, so a read-only key does not see the write tools in tools/list and is refused them by name on tools/call.
What the agent is told, in the endpoint's own instructions: it cannot set a deadline; it must never record a notification as sent unless the human has said it was sent; it should say plainly when a deadline falls at a weekend, because the clocks do not pause; and NIS2's binding deadlines are the Member State's, which this register does not model.