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/incidents/{id}/draft?regime=&stage= | A stage's report draft from the record, gaps listed in to_complete (1.1.0; records nothing) | |||
GET /api/risks | The risk register, highest score first, with the NIS2 Art. 21(2) areas holding no open risk (1.1.0) | |||
GET /api/documents | The document hub: each document's version in force, its SHA-256 and review state (1.1.0) | |||
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.
Regimes: cra_vuln, cra_incident, nis2, and since 1.1.0 gdpr and dora. For those two, send personal_data_breach_aware_at_utc (GDPR's anchor; left out, aware_at_utc is used) and classified_major_at_utc (DORA's: the initial notification is due 4 hours after it, and no later than 24 hours after awareness when classified within them). DORA stage keys: initial, intermediate, final; GDPR: notify.
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"
}
**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"*. Pasted as-is, a block written for another client 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 |
draft_report | read | A stage's draft from the incident record, gaps marked; for a person to send (1.1.0) |
list_risks | read | The risk register and the Article 21(2) areas with no open risk (1.1.0) |
list_documents | read | The document hub: versions in force, hashes, review state (1.1.0) |
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.
Connect it to Zapier, Make or n8n
Cyresora talks to other tools without a plugin: it sends a webhook (an HTTPS message) the moment something happens, and it accepts calls to the API above. Zapier, Make and n8n can use both.
Before you start
- In Cyresora, open Settings -> API & agents. Mint an API key (choose Read only if the other tool only reads), and add a webhook when you have its URL (below).
- Use your install's full address, for example
https://cyresora.yourcompany.com.
When something happens in Cyresora: send it to another tool
- In the other tool, create the trigger that gives you a URL to receive webhooks: - Zapier: the "Webhooks by Zapier" app, trigger Catch Hook (Webhooks by Zapier needs a paid Zapier plan). - Make: module Webhooks → Custom webhook. - n8n: the Webhook node.
- Copy that URL into Cyresora under Settings -> API & agents, and choose the event or events below.
- Make the event happen once in Cyresora, so the other tool receives a sample and you can map its fields.
| Event | Fires when |
|---|---|
incident.opened | , and , HMAC-signed with |
notification.recorded | , and , HMAC-signed with |
deadline.alert | , and , HMAC-signed with |
Every delivery carries X-Cyresora-Signature, an HMAC-SHA256 signature over the raw body. Zapier and Make accept the delivery without checking it, so keep the webhook URL private. In n8n you can check it in a Code node before using the data.
When something happens elsewhere: act in Cyresora
- Zapier: "Webhooks by Zapier", action POST (or Custom Request).
- Make: module HTTP → Make a request.
- n8n: the HTTP Request node.
Send the request to your install's address plus the path from the Endpoints table above, with the header Authorization: Bearer <your API key> and a JSON body. A read-only key cannot write.
The endpoints that change something:
POST /api/incidents: Open an incident; the clocks start fromaware_at_utcPOST /api/incidents/{id}/notifications: Record a notification you have sent
Example recipes
- incident.opened → a row in Google Sheets: webhook trigger on
incident.opened(, and , HMAC-signed with), then the Google Sheets "Create row" action with the fields you want. - notification.recorded → a message in Slack or Teams: webhook trigger on
notification.recorded(, and , HMAC-signed with), then the Slack or Microsoft Teams "Send message" action. - A new form entry or row elsewhere → Cyresora: the other tool's trigger (for example a new Google Sheets row or a form submission), then an HTTP action calling
POST /api/incidents(Open an incident; the clocks start fromaware_at_utc).
These recipes use only the events and endpoints documented above. We have not run them inside Zapier, Make or n8n ourselves; Cyresora's own automated tests check its API.