Ingest API
Push Evidence Events from any system with a tenant API key. The Evidence SDK is the client for this contract — exporters dogfood it rather than posting HTTP themselves.
The canonical envelope is native JSON only (not CloudEvents, not OTLP). Schema, starter catalog, and validated examples: Evidence Event v1.
Endpoint
POST https://api.evitrus.dev/ingest
Authorization: Bearer evt_live_…
Content-Type: application/jsonAccepts a single event object or an array. Response:
{ "accepted": 1 }Same id + same sealed body retries with { "accepted": 1, "duplicate": true }. Same id + a different body is 409. Omit id only when a retry is not required (ingest assigns ev_ + hex). Oversize bodies are 413 (never truncated). Ignore-rule matches are 422 {"ignored":true} — do not retry.
Authentication
| Mode | Header |
|---|---|
| API key (exporters / CI) | Authorization: Bearer evt_live_… |
| Session (product UI tools) | Authorization: Bearer <session token> |
The tenant is always derived from the credential — never from the body.
Event shape
Minimal useful payload (flat aliases are fine; identities is the join plane):
{
"id": "sdk_payments-api_a1b2c3d4e5f6_artifact",
"timestamp": "2026-07-18T14:22:00Z",
"source": "sdk",
"type": "build.artifact.published",
"identities": {
"service": "payments-api",
"commit": "a1b2c3d4e5f67890",
"artifact": "sha256:9f2b…"
},
"summary": "Built payments-api",
"tags": ["service:payments-api", "team:payments"],
"artifacts": [
{
"type": "container_image",
"name": "ghcr.io/acme/payments-api:a1b2c3d",
"digest": "sha256:9f2b…"
}
]
}Do not send contentHash, signature, receivedAt, or changeId — ingest owns those. Unknown extra fields are kept.
| Field | Notes |
|---|---|
id | Stable producer id. Required for idempotent retries. |
type | SDLC catalog name (plan.item.*, build.change.*, test.*, deploy.*, operate.*). Ingest rewrites legacy aliases (pr.merged, ticket.created, deploy.rolledback, …). Canonical rollback is deploy.rolled_back. |
identities | Join plane: service, workItem, changeRequest, commit, artifact, environment, deployment. All optional. |
service / commitSha / environment | Legacy flat aliases of identities.* |
actors | [{ "type": "human"|"service"|"ai", "id", "display" }] |
tags | key:value strings for filtering |
artifacts | Images, packages, etc. Digests prove Test → Deploy |
metadata | Free-form JSON. Stamp metadata.mapper {name, version} |
timestamp | Evidential RFC3339 (source time). Server time used if omitted |
source | Originating system (github, kubernetes, sdk, …) |
Full field rules: data model.
curl example
curl -sS https://api.evitrus.dev/ingest \
-H "Authorization: Bearer $EVITRUS_KEY" \
-H "Content-Type: application/json" \
-d '{
"id": "sdk_payments-api_a1b2c3d4e5f6_smoke",
"source": "sdk",
"type": "build.artifact.published",
"identities": {"service": "payments-api", "commit": "a1b2c3d4e5f6"},
"summary": "Manual smoke event"
}'Ignore rules
Matching events are not stored. Ingest responds 422 with {"ignored":true} — do not retry. Tenant rules are type + optional tags (same When matcher as controls: exact or deploy.*).
Customer-hosted exporters poll the same list:
GET https://api.evitrus.dev/ingest/config
Authorization: Bearer evt_live_…{ "ignore": [{ "type": "deploy.progressed", "tags": ["env:dev"] }] }Poll failures are fail-open: POST anyway; ingest still enforces.
SDK
Use the Evidence SDK for custom emitters. Built-in exporters (Kubernetes, GitHub App, EventBridge, Terraform, GitHub Action) call the same client.
JavaScript (@evitrus/sdk)
const { emit, eventId, finishEvent } = require('@evitrus/sdk')
const event = finishEvent({
id: eventId('sdk', 'payments-api', 'feature-flag'),
type: 'operate.config.changed',
service: 'payments-api',
source: 'sdk',
summary: 'Feature flag updated',
}, 'custom')
await emit(event)Go (github.com/Evitrus/exporter/sdk)
client := sdk.New("https://api.evitrus.dev/ingest", os.Getenv("EVITRUS_API_KEY"))
_, err := client.Send(ctx, sdk.Finish(sdk.Event{
ID: sdk.EventID("sdk", "payments-api", "feature-flag"),
Type: "operate.config.changed",
Service: "payments-api",
Source: "sdk",
Summary: "Feature flag updated",
}, "custom"))Python
from evitrus import emit, event_id, finish_event
emit(finish_event({
"id": event_id("sdk", "payments-api", "feature-flag"),
"type": "operate.config.changed",
"service": "payments-api",
"source": "sdk",
"summary": "Feature flag updated",
}, mapper="custom"))Authenticate with EVITRUS_API_KEY (or pass the key explicitly). Default ingest URL is https://api.evitrus.dev/ingest. The SDK polls GET {ingest}/config and treats ingest 422 {ignored:true} as a successful skip.
Sources: Evitrus/exporter → sdk/.