API reference
The falak-neo-core REST and JSON API — authentication, tenancy, findings, events, agents, policies and the event stream.
Core exposes a REST/JSON API. The console is a client of it, and so can you be.
Base URL and versioning
https://api.eu.falakneo.example/v1The version is in the path. Breaking changes get a new version; additive changes do not. Unknown fields in a response should be ignored rather than treated as an error.
Authentication
curl -H "Authorization: Bearer $FALAK_API_KEY" \
https://api.eu.falakneo.example/v1/findingsCreate keys under Settings → API keys. Keys are stored as SHA-256 digests — the plaintext is shown exactly once, at creation.
There is no tenant parameter
The tenant is derived from the key. No endpoint accepts a tenant id, and passing one has no effect. This is deliberate: a tenant that can be named is a tenant that can be guessed.
Scopes
| Scope | Grants |
|---|---|
findings:read | Read findings and their evidence |
findings:write | Set disposition, status and comments |
events:read | Query the OCSF event store |
agents:read | Read the sensor fleet |
agents:write | Modify agent labels and policy binding |
policies:read / policies:write | Read and publish sensor policy |
rules:read / rules:write | Read and modify detection content |
enrollment:write | Create enrollment tokens |
response:execute | Request signed response actions |
Rate limits
| Plan | Requests / min | Burst |
|---|---|---|
| Team | 600 | 60 |
| Business | 3,000 | 300 |
| Enterprise | negotiated | negotiated |
Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. A 429 carries Retry-After.
Findings
List
GET /v1/findings
?severity=4,5
&status=NEW,TRIAGING
&technique=T1620
&from=1763372400000&to=1763458800000
&limit=100&cursor=…{
"rows": [
{
"findingUid": "b7f1c9e2-…",
"title": "Fileless execution from an anonymous memory file descriptor",
"severityId": 5,
"riskScore": 88,
"status": "NEW",
"disposition": "UNKNOWN",
"techniqueUids": ["T1620", "T1059.004"],
"hostname": "ip-10-40-118-14.eu-central-1.compute.internal",
"k8sNamespace": "payments",
"processName": "memfd:payload",
"firstSeenAt": 1763458204119,
"lastSeenAt": 1763458204119,
"occurrences": 1
}
],
"total": 41,
"nextCursor": "eyJvIjoxMDB9"
}Get one, with evidence
GET /v1/findings/{findingUid}Returns the full OCSF 2004 Detection Finding document plus the correlated evidence events, the reconstructed process tree, comments and response history.
Triage
PATCH /v1/findings/{findingUid}
Content-Type: application/json
{ "status": "RESOLVED",
"disposition": "TRUE_POSITIVE",
"note": "Confirmed. Host isolated, image pulled from the registry." }Every mutation is written to the tenant audit log with the API key as actor.
Events
POST /v1/hunt/query
Content-Type: application/json
{
"classUids": [1007],
"conditions": [
{ "field": "process.name", "operator": "in", "value": "sh,bash,dash" },
{ "field": "actor.process.name", "operator": "in", "value": "nginx,php-fpm" }
],
"join": "and",
"from": 1763372400000,
"to": 1763458800000,
"limit": 500
}The response includes the compiled ClickHouse query core executed, which is useful both for debugging and for understanding cost.
The tenant predicate is added by core
It is always the leading term in the compiled WHERE, so the primary key prefix is used. You cannot remove it, and you cannot widen past it.
Live stream
Server-Sent Events. One-directional, survives HTTP/2 multiplexing and egress proxies far better than a socket upgrade, and reconnects on its own.
curl -N -H "Authorization: Bearer $FALAK_API_KEY" \
"https://api.eu.falakneo.example/v1/events/stream?class_uid=2004&min_severity=4"event: ready
data: {"tenant":"northwind","minSeverity":4}
event: finding
id: 1
data: {"findingUid":"b7f1c9e2-…","severityId":5,…}
: keepaliveAgents
GET /v1/agents # fleet, with probe state and drift
GET /v1/agents/{agentUid} # detail, host facts, SensorStats series
PATCH /v1/agents/{agentUid} # labels, policy bindingPolicies
GET /v1/policies
POST /v1/policies/{id}/publish # compiles, signs, increments generationPublishing returns the new generation. Convergence is observable through the agent list — each agent reports the generation it has applied.
Response actions
POST /v1/actions
Content-Type: application/json
{ "findingUid": "b7f1c9e2-…",
"agentUid": "9f2c1ab4-…",
"typeId": 1,
"params": { "pid": "41290" },
"dryRun": false }typeId | Action |
|---|---|
| 1 | Kill process |
| 2 | Isolate host |
| 3 | Quarantine file |
| 4 | Collect artifact |
| 5 | Stop container |
| 6 | Block network |
| 7 | Run diagnostic |
Core signs the action and stamps an expiry. The sensor verifies the signature before executing and refuses anything unsigned, expired, or addressed to a different agent. 202 Accepted means the action was dispatched, not that it succeeded — poll the finding for the result.
Errors
{ "error": "forbidden",
"message": "API key lacks scope \"response:execute\"",
"requestId": "req_9c2f1ab4" }| Status | Meaning |
|---|---|
| 400 | Malformed request or failed validation |
| 401 | Missing or invalid key |
| 403 | Key lacks the required scope |
| 404 | Not found in your tenant |
| 409 | Conflict, e.g. publishing a policy concurrently |
| 429 | Rate limited |
| 5xx | Our fault; requestId is what support will ask for |
A 404 is returned for objects that exist in another tenant. Distinguishing "does not exist" from "not yours" would itself be a disclosure.
Something wrong or missing? Edit this page