# SIEM export

> Your audit log, security signals and staff access log as one ordered stream: pulled as NDJSON with a cursor, or pushed to Splunk, Datadog or your own HTTPS endpoint.

Source: https://infro.io/docs/security/siem-export

> **Warning:** **On the roadmap — not available yet.** The SIEM export is built and in testing, and not deployed. This page describes what it will ship with.

The SIEM export sends your organization's security record to your own logging or SIEM tooling, as one ordered stream of events from three sources: the [audit log](https://infro.io/docs/org/audit-log), the [security signals](https://infro.io/docs/security/signals), and the [staff access log](https://infro.io/docs/security/staff-access). You can **pull** it with a management token (the management API is on the Startup plan and up), or have INFRO **push** it to Splunk, Datadog or an HTTPS endpoint of yours (every plan).

## Events

Every event carries `id` (`<stream>:<id>`, stable, for de-duplication), `stream` (`audit`, `signals` or `staff_access`), `organization_id` and `occurred_at` (UTC, to the microsecond). Then, by stream:

| Stream | Fields |
| --- | --- |
| `audit` | `action`, `actor` (`type`, `id`, `label`), `target` (`type`, `id`), `ip`, `user_agent`, `metadata`, and `access_change` (`reason`, `severity`) when the entry widened access, or `null`. |
| `signals` | Each `security.*` signal: `event_type`, `severity`, `status`, `scope` (`kind`, `id`), `title`, `sentence`, `figures` and `console_path`. Sent once, as it was first raised, with its status at the time of export: a later acknowledgement, or a warning upgraded to critical, is not sent again. |
| `staff_access` | `kind`, `method`, `route`, `description`, `operator_role`, `operator_ref`, `status`, `count` and `last_at`, as the [staff access log](https://infro.io/docs/security/staff-access) shows them. |

Events are ordered by time, then by stream, then by id, so a page never repeats or skips one. An event is exported once it is 60 seconds old: a record written at one instant can be saved a moment after one written later, and waiting keeps the stream from stepping past it. The stream is append-only, like the audit log.

## Pull

`GET /manage/v1/security/export` returns `application/x-ndjson`: one event per line.

- `streams` (string) — Comma-separated: `audit`, `signals`, `staff_access`. Default all three.
- `cursor` (string) — Resume after the last event of a previous page: the value of its `X-INFRO-Next-Cursor` header, unchanged. Not with `since`.
- `since` (string) — Without a cursor, start at this ISO 8601 time, at most 400 days ago. Default 24 hours ago.
- `limit` (integer) — Events per page, 1 to 1,000. Default 500.

- Headers: `X-INFRO-Next-Cursor` (where the next page starts; keep it even when a page is empty), `X-INFRO-Has-More` (`true` when more events are ready now) and `X-INFRO-Export-Events` (the events in this page).
- Poll with the last cursor you were given. A cursor names a position, not a time, so polling with it never loses an event.
- The token needs `security:read`, and `audit:read` as well when the `audit` stream is included, which it is by default; its creator needs the roles that read the Security screen and, for the audit stream, the audit log. A project-restricted token is refused with `403`.
- `400` `invalid_request` for a cursor INFRO did not issue, an unknown stream, `cursor` and `since` together, or a `since` more than 400 days ago.
- Each stream reaches back as far as its own records are kept: staff access 400 days, signals and the audit log as their own pages say.

```bash
curl -sD headers.txt https://api.infro.io/manage/v1/security/export?streams=audit,signals \
  -H "Authorization: Bearer $INFRO_MANAGEMENT_TOKEN" > events.ndjson
grep -i x-infro-next-cursor headers.txt
```

## Push

A **destination** is where INFRO sends the stream itself. Every five minutes, INFRO sends each enabled destination what is new since it last succeeded: up to three batches of 500 events a run. An organization can have up to five.

| Kind | You give | INFRO sends |
| --- | --- | --- |
| `splunk_hec` | `url` (your HTTP Event Collector, e.g. `https://splunk.example.com:8088/services/collector/event`) and `token` | `POST` with `Authorization: Splunk <token>`, one HEC event per line: `{time, host: "infro", source: "infro", sourcetype: "infro:<stream>", event}`. |
| `datadog_logs` | `datadog_site` (`datadoghq.com`, `us3.datadoghq.com`, `us5.datadoghq.com`, `datadoghq.eu`, `ap1.datadoghq.com`, `ap2.datadoghq.com` or `ddog-gov.com`) and `api_key` (a 32-character Datadog API key, not an application key) | `POST https://http-intake.logs.<site>/api/v2/logs` with `DD-API-KEY`, a JSON array of `{ddsource: "infro", service: "infro", hostname: "infro", ddtags: "stream:<stream>,organization_id:<id>", date, message, infro: <event>}`. |
| `https` | `url`. INFRO issues the signing secret (`whsec_…`) and shows it once. | `POST` of NDJSON (`application/x-ndjson`) with `INFRO-Signature` and `INFRO-Event-Count`. |

- Every request carries `User-Agent: INFRO-SIEM/1`. An HTTPS batch is signed like INFRO's webhooks: `INFRO-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">` with the destination's secret; verify it as the [webhooks page](https://infro.io/docs/api/webhooks#signature-verification) shows, over the raw body. Rotating the secret replaces it at once.
- A destination starts from the moment it is created; use the pull export for anything earlier.
- The URL must be a public HTTPS address. It is checked when you save it and again before each send. INFRO never follows a redirect and waits 10 seconds for an answer.
- The position moves on only when the destination answers 2xx. A failed batch is sent again on the next run, so a receiver can see an event twice: de-duplicate on `id`.
- Each failure is recorded with a reason you can act on ("refused the credential (status 401)", "no answer within 10 seconds"), never the receiver's response. After 20 failures in a row the destination is turned off; turning it on again resumes from where it stopped.
- The token, API key or signing secret is encrypted at rest, bound to the destination, and never shown again: the console shows its last four characters.

## Destinations API

Through the console session. Everyone who opens the Security screen lists destinations with `GET /console/security/siem-destinations`; owners, admins and Security members add, change and remove them. Members limited to projects are refused, and changes are refused while the organization is held or locked for a lapsed plan.

- `name` (string, required) — 1 to 80 characters.
- `kind` (string, required) — `splunk_hec`, `datadog_logs` or `https`.
- `streams` (string[]) — Any of `audit`, `signals`, `staff_access`. Default all three.
- `url` (string) — Splunk and HTTPS only.
- `token` (string) — Splunk only: the HTTP Event Collector token.
- `api_key` (string) — Datadog only.
- `datadog_site` (string) — Datadog only.

- `POST /console/security/siem-destinations` returns `201` with `destination` and `signing_secret` (the HTTPS secret, shown this once; `null` for the other kinds). A sixth destination is refused with `400` `invalid_request`, code `siem_destination_limit`.
- `PATCH /console/security/siem-destinations/{id}` takes `name`, `enabled`, `streams`, `url`, `token`, `api_key`, `datadog_site`, or `rotate_secret: true` (HTTPS only, which returns the new `signing_secret`). Changing a Splunk destination's `url` needs its `token` in the same request (`400` `credential_required` otherwise), so a stored token is never sent to a new address. `DELETE` removes it. An unknown id is `404` `resource_not_found`.
- A destination shows `id`, `name`, `kind`, `enabled`, `streams`, `url`, `datadog_site`, `credential_last4`, `delivered_through` (the newest event delivered), `last_delivered_at`, `last_attempt_at`, `last_error`, `consecutive_failures`, `created_at` and `updated_at`.
- Each change is in the audit log (`siem_destination.created`, `siem_destination.updated`, `siem_destination.deleted`). Adding a destination, or pointing one at a new URL or site, is listed as an access change on the Security screen: your security record now goes somewhere new.

A destination is your own tool, chosen and contracted by you, like your AI providers: INFRO sends it what you configured and nothing else.
