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
- Open the Culprit dashboard → Services → select the service.
- In the Ingest token section, click Issue.
- 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) |