Ingest events
POST /v1/events records custom experimentation events attributed to a variable and variation arm. Use this to capture metrics (purchases, clicks, conversions, numerical values) against an experiment for the results charts.
Request
POST /v1/events HTTP/1.1
Host: api.redpennon.dev
X-API-Key: 00000000-0000-0000-0000-000000000000
Content-Type: application/json
{
"events": [
{
"event": "purchase",
"variable": "checkout-flow",
"variation": "variant-a",
"user": { "id": "user-123" },
"value": 49.99,
"occurred_at": "2026-05-19T02:30:00Z",
"evaluation_trace": "rpe_v1:..."
}
]
}
Body
| Field | Type | Required | Description |
|---|---|---|---|
events | object[] | yes | Array of event objects (see fields below). May be empty — returns 202 {"accepted": 0}. |
Event object fields
| Field | Type | Required | Description |
|---|---|---|---|
event | string | yes | Event name (e.g. "purchase", "button_clicked"). |
variable | string | yes | Variable key — must resolve to a variable in the API key's project. |
variation | string | yes | Variation slug — must exist on the variable's parent feature. |
user | object | no | User context. Same shape as the evaluation user object. Omit or send {} for anonymous. |
value | number | no | Numerical measurement attached to the event (e.g. revenue, duration). |
occurred_at | string | no | ISO-8601 timestamp of when the event occurred. Defaults to ingest time. |
evaluation_trace | string | no | Token from a prior POST /v1/variables/{key} response. When supplied and valid, the stored event is marked evaluation_trace_verified = true, confirming the event came from a real, recently-verified evaluation. |
Closing the attribution loop
To get evaluation_trace_verified = true on an event:
- Call
POST /v1/variables/{key}with a stable user (idoremail). - The response includes
evaluation_tracewhen a variation was served. - Pass that string as
evaluation_tracein the corresponding event row.
The token is environment- and variation-scoped with a short TTL (default 5 minutes), so stale or replayed tokens are rejected silently — the event is still accepted, just not verified.
Response
202 Accepted
{ "accepted": 3 }
accepted is the number of events written. Validation errors on any event in the batch cause the entire request to be rejected before writes begin (400).
Errors
Validation runs before governance. The first invalid event short-circuits the entire batch.
| Status | Body | When |
|---|---|---|
400 | {"error": "\"events\" field is required."} | Body has no events key. |
400 | {"error": "\"events\" must be a list."} | events is not an array. |
400 | {"error": "events[N] must be an object."} | An entry is not a JSON object. |
400 | {"error": "events[N].event is required."} | event field is blank or missing. |
400 | {"error": "events[N].variable is required."} | variable field is blank or missing. |
400 | {"error": "events[N].variation is required."} | variation field is blank or missing. |
400 | {"error": "events[N].variable resolves to unknown variable."} | variable key doesn't resolve in this project. Unlike evaluation, unknown variables are not fail-open here. |
400 | {"error": "events[N].variation is not defined on feature."} | variation slug doesn't exist on the variable's parent feature. |
400 | {"error": "events[N].value must be numeric when set."} | value was sent as a non-numeric type. |
400 | {"error": "events[N].occurred_at must be ISO-8601 when set."} | occurred_at could not be parsed. |
401 | {"error": "Invalid or missing API key."} | Missing or invalid X-API-Key. |
403 / 429 | {"error": "...", "code": "..."} | Governance (see Errors). Governance runs after per-event validation. |
Example: cURL
export REDPENNON_API_KEY=00000000-0000-0000-0000-000000000000
curl -X POST "https://api.redpennon.dev/v1/events" \
-H "X-API-Key: $REDPENNON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [
{
"event": "purchase",
"variable": "checkout-flow",
"variation": "variant-a",
"user": { "id": "user-123" },
"value": 49.99
}
]
}'