Docs / Integrations

Integrations

The finding schema on the way out, and the ingest event shape on the way in for the cloud tier.

Status: preview. The cloud ingest endpoint accepts the event shape below, but the path from ingested events to a verdict is being fixed. Build against the contract, not against a verdict, until this note is gone.

Findings: OCSF, out

Every analysed build returns one result, benign or malicious, delivered as an OCSF Detection Finding (class_uid: 2004): an open, vendor-neutral schema most SIEMs already parse, over a webhook or log file. Abridged example:

{
  "class_uid": 2004,
  "class_name": "Detection Finding",
  "category_uid": 2,
  "time": 1783598400000,
  "metadata": { "version": "1.1.0", "product": { "name": "protet" } },
  "finding_info": { "uid": "3f8a1c2e-..." },
  "container": { "pod_name": "ci-build-7f3a", "name": "c1a2b3..." },
  "cloud": { "region": "build-runners" },
  "enrichments": [
    { "name": "finding_kind", "value": "pod" },
    { "name": "verdict", "value": "malicious" },
    { "name": "pod_uid", "value": "3f8a1c2e-..." },
    { "name": "client_id", "value": "..." }
  ]
}

verdict is malicious or benign; read it rather than treating the presence of a finding as an alert. pod_uid, container.pod_name and cloud.region (the namespace) identify the build; client_id is your account.

Cloud ingest auth: refresh & access tokens

Mint a long-lived refresh token from your dashboard on the cloud plan. Your collector keeps that secret and trades it for a short-lived access token (valid ~1 hour) — that access token is what you send events with. Refresh shortly before it expires; a build longer than the token's lifetime just keeps refreshing, and the session isn't interrupted.

POST https://protet.io/v1/ingest/refresh
Authorization: Bearer <your refresh token>

→ { "token": "<access token>", "expires_in": 3600 }

Suspension takes effect at the next refresh (the endpoint re-checks your entitlement every time), so a suspended account simply stops getting new access tokens. To revoke a refresh token yourself (for example one that leaked from CI), use Revoke all refresh tokens in your dashboard or POST /portal/ingest-token/revoke-all: every refresh token minted before that stops working at its next refresh, and access tokens already issued expire within about an hour. Mint a new refresh token afterwards and update INGEST_REFRESH_TOKEN. Refresh tokens minted before revocation was introduced keep working until your first revoke. A collector configured with INGEST_REFRESH_TOKEN and INGEST_REFRESH_URL (shown in your dashboard) does all of this for you; the values are listed in the Configuration reference. To try the exchange by hand, use the Quickstart.

Custom telemetry: the ingest event shape, in (cloud tier)

The cloud tier's ingest endpoint is a deliberately open contract — you're not required to run our collector. Any client that authenticates with a valid access token and POSTs this shape is accepted:

POST https://ingress.protet.io/v1/ingest
Authorization: Bearer <your access token>
Content-Type: application/json

{
  "events": [
    {
      "event_type": "exec",
      "node": "node-1",
      "pod_uid": "3f8a1c2e-...",
      "pod_name": "ci-build-7f3a",
      "namespace": "build-runners",
      "container_id": "c1a2b3...",
      "pid": 4821,
      "ppid": 4802,
      "exec_id": "4821-1720512345",
      "binary": "/usr/local/bin/npm",
      "args": "install",
      "ts": "2026-07-09T12:00:00Z",
      "collected_at": "2026-07-09T12:00:00.120Z"
    }
  ]
}

Why binary and args are separate

Send the executable path and its arguments as two fields — not pre-joined into one command string. A request carrying a joined cmd field is rejected with 422 naming these fields.

event_type: "exit" events carry neither field — just the pid that ended.

client_id and your plan entitlements are stamped server-side from the access token itself — never trust or accept them from the client payload if you're building your own collector against this endpoint.