BricqsBricqs

Events ingestion

Events are the entry point for action data. One synchronous POST: the event persists as a fact and is evaluated inline before the response returns. The response itself is narrow (ids, status, and rule-granted rewards); the full downstream effect shows up in participant state, webhooks, and the SSE stream. This page covers the two endpoints, idempotency, batching, and retry behavior.

Last updatedJuly 2026

Key takeaways

Quick read
  • Two endpoints, both synchronous: POST /gamify/events (single) and POST /gamify/events/batch (up to 100). Processing completes before the response returns.
  • Idempotency-Key header makes every retry safe. Keyless requests get a server-synthesized key, so accidental retries never double-credit.
  • Auth is dual-path: X-API-Key from your backend (events:write scope, included in the mint default), or a participant JWT from the browser, pinned to its own participant_id.
  • Per-participant rate limit: 100 events/minute by default. Batched events count individually; over-limit items are marked rate_limited, not the whole batch.
  • One event per user action. The rules engine fans it out; you almost never call points or badges endpoints directly from an event flow.

Endpoints

Two ways to send events

POST /gamify/events

Synchronous single event. Persists the fact, evaluates rules inline, and returns event_id, fact_id, status, plus rewards_granted (rules-engine action outcomes only). The default for interactive flows.

POST /gamify/events/batch

Synchronous batch of 1 to 100 events. Each event processes independently with per-item status; one bad event does not fail the request.

Single event

The default path

One event, one POST, processed before the response returns. The body carries ids, status, and any rule-granted rewards; for the full picture of what changed (challenge progress, tier moves), read participant state or listen on webhooks/SSE.

POST /api/v1/gamify/events·bash
curl -X POST https://api.bricqs.co/api/v1/gamify/events \
  -H "X-API-Key: bq_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: user_42:order_4271" \
  -d '{
    "participant_id": "user_42",
    "event_name": "purchase_completed",
    "properties": {
      "amount": 1250,
      "currency": "INR",
      "category": "skincare"
    },
    "timestamp": "2026-07-19T10:42:00Z"
  }'

# response (synchronous; rules already evaluated)
HTTP/1.1 200 OK
{
  "event_id": "01K0...",
  "fact_id": "01K0...",
  "status": "processed",
  "rewards_granted": []
}

From a browser or mobile app, send a participant JWT in the Authorization header instead of X-API-Key; the token is pinned to its own participant_id. Field reference: /docs/gamification-api/events.

Idempotency

Retries are safe by default, safer with a key

Networks fail and clients retry. Two layers protect you: the server synthesizes a dedupe key when you send none, and an explicit Idempotency-Key header gives you full control for offline replay.

text
Header: Idempotency-Key
Format: 1 to 255 chars matching [A-Za-z0-9_-:.]

Recommended shape: <participant>:<subject>[:<period>]
  user_42:order_4271                  one-shot purchase
  user_42:daily_login:2026-07-19      daily action (one per day)
  user_42:quiz_fit_basics:attempt_3   distinct attempts, distinct keys

Behavior:
  Same key again        = the stored event's ids come back with
                          status: "duplicate". No new fact, no double-credit.
                          rewards_granted is empty on duplicates (rule
                          outcomes are not replayed in the response).
  No key supplied       = the server synthesizes one from the event's
                          identity (synth: prefix). Accidental retries of
                          the identical request deduplicate; deliberate
                          re-sends with a changed payload do not.
  Header AND body key   = must match, else 400 IDEMPOTENCY_KEY_MISMATCH.

Offline replay (mobile): queue events locally WITH explicit keys, then
replay the queue on reconnect. Every already-delivered event dedupes and
comes back with status: "duplicate".

Batch

Up to 100 events in one POST

Use batch for reconnect replays and server-side fan-out. Each event carries its own idempotency_key in the body (the request-level header is not applied to batches).

POST /api/v1/gamify/events/batch·bash
curl -X POST https://api.bricqs.co/api/v1/gamify/events/batch \
  -H "X-API-Key: bq_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "participant_id": "user_42",
        "event_name": "lesson_completed",
        "properties": { "lesson_id": "l_001" },
        "idempotency_key": "user_42:l_001"
      },
      {
        "participant_id": "user_91",
        "event_name": "lesson_completed",
        "properties": { "lesson_id": "l_002" },
        "idempotency_key": "user_91:l_002"
      }
    ]
  }'

# response: per-item results in submission order
HTTP/1.1 200 OK
{
  "processed": 2,
  "failed": 0,
  "results": [
    { "event_id": "01K0...", "fact_id": "01K0...", "status": "processed", "rewards_granted": [] },
    { "event_id": "01K0...", "fact_id": "01K0...", "status": "processed", "rewards_granted": [] }
  ]
}

Per-item statuses you must handle: processed, error (schema/processing failure), rate_limited (that participant is over their per-minute cap), and forbidden (JWT callers only: the event's participant_id does not match the token). The rest of the batch always continues.

Retry policy

What to do when ingestion fails

text
Status codes:

  200 OK              Processed, or status "duplicate" for a repeated key.
  400 Bad Request     IDEMPOTENCY_KEY_INVALID / IDEMPOTENCY_KEY_MISMATCH.
                      Do NOT retry; fix the key.
  401                 AUTH_UNAUTHENTICATED / AUTH_API_KEY_INVALID.
                      Do NOT retry; fix auth.
  403                 AUTH_FORBIDDEN. API key lacks events:write, or a JWT
                      tried to emit for another participant. Fix scopes.
  422                 Schema validation (empty batch, >100 events, bad
                      field). Do NOT retry; fix the payload.
  429                 RATE_LIMIT_PARTICIPANT_EXCEEDED (or tenant/IP tier).
                      Retry after the Retry-After header value.
  500/502/503/504     Server error. Retry with exponential backoff and
                      jitter; your idempotency key makes this safe.

Every error body uses the standard envelope:
  { "error": { "type": "...", "code": "...", "message": "...",
               "request_id": "req_..." } }
Log request_id; support can trace any request by it.

Common mistakes

What goes wrong on first integration

01Mistake

Treating ingestion as fire-and-forget async, or expecting challenge/tier details in the ingestion response.

Fix

Ingestion is synchronous, but the response carries only ids, status, and rule-granted rewards. For celebration UI, refresh state (GET /gamify/state/{participant_id} or the SDK hooks) or subscribe to the SSE stream; progression webhooks carry the details server-side.

02Mistake

Reusing one idempotency key for repeatable actions. Day 2 of a streak deduplicates into day 1.

Fix

Scope keys to the occurrence: user:action:date for dailies, user:action:attempt_n for attempts.

03Mistake

Batching 100 events for one participant and expecting them all to land.

Fix

The per-participant limit (default 100/min) applies per event. Spread large per-user replays over time and handle rate_limited items by re-queueing.

04Mistake

Calling points/badges endpoints directly from event flows, double-crediting what the rules engine already granted.

Fix

One event per user action; let rules fan out. Reserve direct value-movement endpoints (which need the gamify:write scope) for server-driven grants like support adjustments.

05Mistake

Ignoring per-item statuses in batch responses.

Fix

Check results[i].status. error means fix and re-send; rate_limited means re-queue with backoff; forbidden means your JWT is emitting for the wrong participant.

Developer FAQ

Common questions when integrating gamification with Bricqs.

Ready to ship?

Wire it up with the Bricqs SDK or API

Headless SDK for React UIs, REST API for any backend. Same engine behind both.

1 brief to align the room2 mechanics max in version one
What happens next
01
Pick the mechanic
Choose the smallest working system for the brief.
02
Launch without rebuilds
Configure rules and rewards in one place.