Events API
Emit custom events from your application. Events are processed synchronously, persisted as facts, and fed through the rules engine to trigger challenge evaluations, badge unlocks, tier transitions, and outbound webhooks. Use the history endpoint to read back a participant’s emitted events with filters and pagination.
AuthAll endpoints require an X-API-Key header. Production keys are prefixed bq_live_; sandbox keys bq_test_.
ScopesParticipant key Browser/mobile callers use a participant JWT (Authorization: Bearer) with the write:events scope for emission and read:state for history; the token is pinned to its own participant_id.
Admin scope Server callers use X-API-Key. Emission requires the events:write scope and the history read requires gamify:read; both are included in the mint default, so a default-minted key covers this whole page.
Base URLhttps://api.bricqs.co/api/v1/gamify
Endpoint inventory
| Method | Endpoint | Purpose |
|---|
| POST | /gamify/events | Emit a single event. Idempotent. |
| POST | /gamify/events/batch | Emit up to 100 events in one request. Per-item idempotency via body keys. |
| GET | /gamify/participants/{participant_id}/events | Paginated event history with filters. |
POST/api/v1/gamify/events
Participant keyIdempotent
Emit a single custom event for a participant. The event is persisted as a fact and evaluated synchronously; it may trigger badge unlocks, tier transitions, and outbound webhooks as downstream effects. The response body is narrow: event_id, fact_id, status, and rewards_granted (rules-engine action outcomes only; challenge/tier detail is read from state, webhooks, or SSE). Safe to retry: with an Idempotency-Key header a second call returns the same event_id and fact_id with status duplicate (rewards_granted empty), and when no key is supplied the server synthesizes one from the event's identity, so accidental retries of an identical request still deduplicate.
Headers
| Field | Type | Description |
|---|
| Idempotency-Key | str | Optional. 1 to 255 chars matching ^[A-Za-z0-9_\-:.]{1,255}$. Recommended for every call so retries never produce duplicate facts. |
Request body
| Field | Type | Description |
|---|
| participant_idrequired | str | Your participant identifier. 1 to 255 characters. Auto-creates the participant on first interaction. |
| event_namerequired | str | Event name to emit. 1 to 100 characters. Use snake_case (purchase_completed, video_watched). |
| properties | dict[str, Any] | Free-form event payload. Read by challenge evaluators and forwarded to outbound webhooks. Default: {} |
| timestamp | Optional[datetime] | Event occurrence time. Defaults to server time (UTC). Naive datetimes are stored as UTC. |
| idempotency_key | Optional[str] | Legacy body idempotency key (max 255 chars). The Idempotency-Key header is preferred; when both are supplied they must match. |
POST /api/v1/gamify/events
X-API-Key: bq_live_xxxxx
Content-Type: application/json
Idempotency-Key: purchase_ord_001
{
"participant_id": "user_42",
"event_name": "purchase_completed",
"properties": {
"amount": 49.99,
"currency": "USD",
"product_id": "prod_abc"
},
"timestamp": "2026-02-14T10:00:00Z"
}
Response fields
| Field | Type | Description |
|---|
| event_id | Optional[str] | Identifier of the persisted event record. |
| fact_id | Optional[str] | Identifier of the fact emitted into the rules pipeline. |
| status | str | Processing status. Default: "processed" |
| rewards_granted | list[dict[str, Any]] | Outcomes of rules-engine actions this event fired (rule-granted points, badges, rewards). Empty when no rule matched, and always empty on duplicates. Challenge progress and tier transitions are NOT reported here; read them from state, webhooks, or SSE. Default: [] |
{
"event_id": "01HZ...",
"fact_id": "01HZ...",
"status": "processed",
"rewards_granted": []
}
Errors
| Status | Code | When it fires |
|---|
| 400 | IDEMPOTENCY_KEY_INVALID | Idempotency-Key header is empty, longer than 255 chars, or contains characters outside [A-Za-z0-9_\-:.]. |
| 400 | IDEMPOTENCY_KEY_MISMATCH | Both the Idempotency-Key header AND a body idempotency_key were provided and they differ. |
| 401 | AUTH_UNAUTHENTICATED | No credential, or the API key / participant JWT is invalid, expired, or revoked (also AUTH_API_KEY_INVALID). |
| 403 | AUTH_FORBIDDEN | API key lacks the events:write scope, a participant JWT emits for a different participant_id, or the tenant is disabled. |
| 429 | RATE_LIMIT_PARTICIPANT_EXCEEDED | Per-participant rate cap exceeded (default 100 events/min, plan-configurable). The Retry-After response header tells you when to retry. |
POST/api/v1/gamify/events/batch
Participant key
Emit up to 100 events in a single request. Each event is processed independently; per-item failures are reported in the results array without failing the whole request. Use the body-level idempotency_key on each event for retry safety (the request-level Idempotency-Key header is not honoured here; keyless items get server-synthesized keys). Every batched event counts against the same per-participant rate limit as the single endpoint: over-limit events are marked rate_limited individually, and for participant-JWT callers any event whose participant_id differs from the token subject is marked forbidden, while the rest of the batch continues.
Request body
| Field | Type | Description |
|---|
| eventsrequired | list[EmitEventRequest] | Array of 1 to 100 events. Each entry uses the same shape as POST /events. Set idempotency_key per item for retry safety. |
POST /api/v1/gamify/events/batch
X-API-Key: bq_live_xxxxx
Content-Type: application/json
{
"events": [
{
"participant_id": "user_42",
"event_name": "purchase_completed",
"properties": { "amount": 49.99 },
"idempotency_key": "purchase_ord_001"
},
{
"participant_id": "user_42",
"event_name": "loyalty_signup",
"properties": { "channel": "email" },
"idempotency_key": "signup_user_42"
}
]
}
Response fields
| Field | Type | Description |
|---|
| processed | int | Count of events that completed successfully. |
| failed | int | Count of events that returned an error. |
| results | list[EmitEventResponse] | Per-item results in submission order. Statuses: processed, error (null event_id/fact_id), rate_limited (participant over cap), forbidden (JWT caller, participant_id mismatch). |
{
"processed": 2,
"failed": 0,
"results": [
{
"event_id": "01HZ...",
"fact_id": "01HZ...",
"status": "processed",
"rewards_granted": []
},
{
"event_id": "01HZ...",
"fact_id": "01HZ...",
"status": "processed",
"rewards_granted": []
}
]
}
Errors
| Status | Code | When it fires |
|---|
| 401 | AUTH_UNAUTHENTICATED | No credential, or the API key / participant JWT is invalid, expired, or revoked. |
| 403 | AUTH_FORBIDDEN | API key lacks the events:write scope, or the tenant is disabled. |
| 422 | VALIDATION_INVALID_VALUE | events array is empty or exceeds 100 entries, or an item fails schema validation (validation_error envelope with a VALIDATION_* code). |
GET/api/v1/gamify/participants/{participant_id}/events
Participant key
Paginated event history for a participant, with optional filters by event_name and date range.
Path parameters
| Field | Type | Description |
|---|
| participant_idrequired | str | Your participant identifier. |
Query parameters
| Field | Type | Description |
|---|
| event_name | Optional[str] | Filter to events with this exact name. |
| from_date | Optional[str] | Inclusive lower bound, ISO date (YYYY-MM-DD). |
| to_date | Optional[str] | Inclusive upper bound, ISO date (YYYY-MM-DD). |
| page | int | 1-indexed page number. Default: 1 |
| page_size | int | Items per page. Max 100. Default: 50 |
GET /api/v1/gamify/participants/user_42/events?event_name=purchase_completed&page=1&page_size=20
X-API-Key: bq_live_xxxxx
Response fields
| Field | Type | Description |
|---|
| participant_id | str | Echo of the requested participant id. |
| events | list[EventHistoryEntry] | Page of events. Each entry: id, event_name, properties (default {}), occurred_at. |
| total | int | Total events matching the filters across all pages. |
| page | int | Echo of the requested page. |
| page_size | int | Echo of the requested page size. |
{
"participant_id": "user_42",
"events": [
{
"id": "01HZ...",
"event_name": "purchase_completed",
"properties": { "amount": 49.99, "currency": "USD" },
"occurred_at": "2026-02-14T10:00:00Z"
}
],
"total": 156,
"page": 1,
"page_size": 20
}
Errors
| Status | Code | When it fires |
|---|
| 401 | AUTH_UNAUTHENTICATED | No credential, or the API key / participant JWT is invalid, expired, or revoked. |
| 403 | AUTH_FORBIDDEN | API key lacks the gamify:read scope (in the mint default; only custom-scoped keys can lack it), or a participant JWT requested another participant's history. |