Generic change events — Ingest Setup

This guide shows how to send change events — deploys, config changes, feature-flag flips — to Culprit from a CI/CD pipeline, deploy script, or config-management tool.

Change events are context, not alerts: they never page anyone, never open incidents, and never count as symptoms. Culprit surfaces them to root-cause analysis as a "Recent changes" block, so when an incident forms shortly after a deploy, the analysis can say so.


Endpoint

POST https://ingest.theculprit.ai/ingest/<tenant_id>/<service_name>/generic-change
Authorization: Bearer <ingest_token>
Content-Type: application/json

Replace <tenant_id> and <service_name> with the values shown on your service's settings page. Send the change under the service that was changed — root-cause analysis still sees it tenant-wide, so a deploy of one service can be cited as the cause of an incident on another.


Getting your ingest token

  1. Open the Culprit dashboard → Services → select the service.
  2. In the Ingest token section, click Issue.
  3. Copy the token shown — it is displayed once. After you navigate away, only the token prefix is visible.

To rotate a compromised token, click Rotate. The previous token remains valid for the grace period shown, giving you time to update your CI/CD configuration without an interruption.


Request body

| Field | Type | Required | Notes | |---|---|---|---| | change_type | "deploy" | "config" | "flag" | "other" | Yes | Any other value is rejected with 400 | | title | string | Yes | Human-readable summary, e.g. "Deployed api-gateway v2.3.1" | | occurred_at | ISO-8601 string | No | Defaults to receipt time. Future-dated values are clamped to receipt time (clock-skew protection). An unparseable value is rejected with 400 — a silently mis-timed change would corrupt the "what changed before this incident?" signal | | actor | string | No | Who or what made the change, e.g. "jane@example.com" or "ci-bot" | | link_url | string | No | Link to the release, PR, or change record | | context | object | No | Free-form extra metadata (commit SHA, pipeline id, …) |

PII in titles, actors, or context (email addresses, IP addresses, API keys) is detected and tokenized before any storage — identical to alert ingestion. The original values are preserved encrypted and are visible only after authentication.


Worked example

At the end of a deploy job:

curl -X POST \
  'https://ingest.theculprit.ai/ingest/<tenant_id>/<service_name>/generic-change' \
  -H 'Authorization: Bearer <ingest_token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "change_type": "deploy",
    "title": "Deployed api-gateway v2.3.1",
    "occurred_at": "2026-07-02T10:14:00Z",
    "actor": "jane@example.com",
    "link_url": "https://github.com/acme/api/releases/v2.3.1",
    "context": { "commit": "abc123", "pipeline": "release" }
  }'

Successful response:

{ "accepted": true, "ids": ["<vault_id>"] }

What Culprit does with change events

  • Stored (tokenized + encrypted) in your tenant's change history.
  • Surfaced to root-cause analysis: when an incident is analyzed, changes from the 60 minutes before the incident's first event through its latest event are listed as "Recent changes" — across all services in your organization, because a deploy of one service routinely breaks another.
  • Never paged, never correlated into incidents, never counted as an alert.

Duplicate sends of an identical body within 5 minutes are deduplicated, and sustained high volume is bounded by the same per-service rate ceiling as alert ingestion.


Error responses

| Status | Body | Meaning | |---|---|---| | 401 | { "error": "missing_auth" } | No Authorization header (and no HMAC signature) | | 401 | { "error": "invalid_ingest_token" } | Token is incorrect or has been rotated/revoked | | 400 | { "error": "unrecognized_payload" } | Body is not valid JSON, title is missing/not a string, change_type is not one of the four values, or occurred_at is present but unparseable | | 403 | { "error": "service_disabled" } | The service exists but monitoring is paused | | 404 | { "error": "service_deleted_or_not_found" } | The service does not exist | | 429 | { "error": "rate_limited" } | Rate ceiling active (sustained high volume) |