Skip to main content

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

FieldTypeRequiredDescription
eventsobject[]yesArray of event objects (see fields below). May be empty — returns 202 {"accepted": 0}.

Event object fields

FieldTypeRequiredDescription
eventstringyesEvent name (e.g. "purchase", "button_clicked").
variablestringyesVariable key — must resolve to a variable in the API key's project.
variationstringyesVariation slug — must exist on the variable's parent feature.
userobjectnoUser context. Same shape as the evaluation user object. Omit or send {} for anonymous.
valuenumbernoNumerical measurement attached to the event (e.g. revenue, duration).
occurred_atstringnoISO-8601 timestamp of when the event occurred. Defaults to ingest time.
evaluation_tracestringnoToken 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:

  1. Call POST /v1/variables/{key} with a stable user (id or email).
  2. The response includes evaluation_trace when a variation was served.
  3. Pass that string as evaluation_trace in 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.

StatusBodyWhen
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
}
]
}'

Example: SDK

See the per-language SDK docs: Node, Python, Go.