corvo.rda.run
git.rda.run/corvo/corvo
Install
contract · v1

What travels between corvo and ninho

A POST with a JSON array. The contract is versioned and it drives the design of both sides: anyone implementing another receiver has to obey this page, not the code.

methodPOST /configured/path
bodyJSON array of envelopes
content-typeapplication/json
encodinggzip, when enabled
authorizationBearer <token>
deliveryat-least-once · idempotent id
envelope · full example
{
  "v": 1,
  "id": "srv-web-01-2841-1756938610123000000",
  "host": "srv-web-01",
  "event": {
    "@timestamp": "2026-09-04T12:30:10.123Z",
    "sequence": 2841,
    "category": "audit-rule",
    "record_type": "syscall",
    "result": "success",
    "session": "5",
    "tags": ["cmds"],
    "summary": {
      "actor":  { "primary": "root", "secondary": "root" },
      "action": "executed",
      "object": { "type": "file", "primary": "/usr/bin/id" },
      "how":    "/usr/bin/id"
    },
    "user": {
      "ids":   { "auid": "0", "uid": "0", "gid": "0" },
      "names": { "auid": "root", "uid": "root", "gid": "root" }
    },
    "process": {
      "pid": "48213", "ppid": "48044", "title": "id -u",
      "name": "id", "exe": "/usr/bin/id", "cwd": "/root",
      "args": ["id", "-u"]
    },
    "data":  { "arch": "x86_64", "syscall": "execve", "exit": "0", "tty": "pts0" },
    "paths": [ { "name": "/usr/bin/id", "nametype": "NORMAL", "inode": "1049" } ]
  }
}

Three typing traps

category and record_type are strings
They are not numbers. "record_type": "syscall", never 1300. Anything expecting an integer breaks on the first batch.
Almost everything is a string
Including pid, uid, gid, exit and inode, because audit delivers those values in varied formats. The exceptions are v and sequence, which are numbers.
data and paths are open maps
The keys vary per syscall. Store them as JSON, index what matters, and do not declare a closed schema for those two.

record_type

syscall
user_auth
user_login
user_cmd
cred_acq
add_user
config_change
service_start
anom_promiscuous
The set is open: treat an unknown record_type as a valid event and store it just the same.
response codes

The status code decides whether the audit survives

503 Infrastructure failure is always 503

Database down, disk full, migration in progress. The agent keeps the batch in the queue, honors Retry-After and delivers when the server comes back. Nothing gets lost.

400 400 only for a malformed batch

A 400 returned because the database went down pulls the batch out of the agent queue and throws it into dead-letter: the costliest mistake a receiver can make, because that audit never comes back.

responsewhat corvo doeswhy
2xxacknowledges and advances the offsetthe only way a batch leaves the queue
429 · 503retry honoring Retry-Afterinfrastructure busy or down, the data is fine
401 · 403retry for 15 min, then dead-letterusually an expired token, not bad data
400 · 404 · 413 · 422immediate dead-letterdoes not improve on repeat and would freeze the queue
no responseinfinite retry with jitterthe network comes back, and giving up means losing audit
auditd → https · nothing gets lost corvo (Portuguese for raven) is the agent, ninho (nest) is the server. Both speak the same versioned contract.
docs how it works corvo (agent) ninho (server)
ninho docs tutorials how-to guides reference explanation
corvo docs tutorials how-to guides reference explanation
reference API contract install metrics and queue
code git.rda.run/corvo/corvo git.rda.run/corvo/ninho MIT license