Amba

Events

Track engagement events — the primary write path for Amba.events.track().

Every Amba.events.track() call sends a POST here. Events drive streak qualification and XP award evaluation server-side; the SDK additionally forwards events to first-party analytics client-side, so this endpoint never double-writes to analytics.

Endpoints

MethodPathDescription
POST/client/eventsTrack one or many events.
POST/client/events/explainDry-run an event: preview which rules would fire. No side effect.

POST /client/events

Accepts either a single event or a { events: […] } batch.

Request — single event

{
  "event": "workout_completed",
  "properties": { "duration_minutes": 30 },
  "timestamp": "2026-04-24T12:00:00Z"
}

Request — batch

{ "events": [{ "event": "…" }, { "event": "…" }] }

Per-event fields (TrackEventInput):

FieldTypeRequiredDescription
eventstringyesEvent name.
propertiesobjectnoArbitrary JSONB properties.
timestampISO-8601noDefaults to now().

Limits

  • Max batch size: 500 events per request. Exceeding returns 413 BATCH_TOO_LARGE.
  • Streak qualification is checked asynchronously after the write — it cannot fail the request.
  • The user's last_seen_at is updated fire-and-forget.

Response 200

{ "data": { "tracked": 30 } }

Empty batch returns { "data": { "tracked": 0 } }.

Errors

  • 413 BATCH_TOO_LARGE — batch exceeds 500 events.
  • 500 TRACK_FAILED.

Try it:

POST/client/events
client auth
curl -X POST 'https://api.amba.dev/v1/client/events' \
  -H 'Content-Type: application/json' \
  -d '{
  "event": "workout_completed",
  "properties": {
    "duration_minutes": 30
  },
  "timestamp": "2026-04-24T12:00:00Z"
}'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/client/events' \
  -H 'X-Api-Key: ${CLIENT_API_KEY}' \
  -H 'Authorization: Bearer ${SESSION_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{"event":"workout_completed","properties":{"duration_minutes":30},"timestamp":"2026-04-24T12:00:00Z"}'

POST /client/events/explain

Dry-run an event against the rule engine and get back which rules would fire and what each would produce — XP awards, currency grants, feed items, streak qualification, and webhook deliveries — without committing any side effect. Use it to confirm your rules are wired the way you intend before sending the real track call.

This route needs only the client API key (no signed-in user). Supply the candidate user as event.user_id to also preview per-user gating (daily limits, cooldowns, balance caps); omit it for the user-independent base match. Nothing is written either way.

Request

{
  "event": {
    "name": "lesson_completed",
    "properties": { "score": 90 },
    "user_id": "f3b1…"
  }
}
FieldTypeRequiredDescription
event.namestringyesCandidate event name to evaluate.
event.propertiesobjectnoCandidate properties — read by property-scaled grant rules.
event.user_idstringnoApp user to evaluate per-user gating against. Omit for the base match.

Response 200

{
  "data": {
    "event": { "name": "lesson_completed", "properties": { "score": 90 }, "user_id": "f3b1…" },
    "matched": [
      { "rule_type": "xp", "rule_id": "…", "name": "Lesson XP", "would": { "xp_delta": 25 } },
      {
        "rule_type": "currency_grant",
        "rule_id": "…",
        "name": "grant coins on lesson_completed",
        "would": { "currency": "coins", "amount": 10 }
      },
      {
        "rule_type": "feed",
        "rule_id": "…",
        "name": "feed item \"completed_lesson\" on lesson_completed",
        "would": { "feed_item": { "action": "completed_lesson", "target_type": "lesson" } }
      },
      {
        "rule_type": "streak",
        "rule_id": "…",
        "name": "Daily Lessons",
        "would": { "period": "daily" }
      },
      {
        "rule_type": "webhook",
        "rule_id": "…",
        "name": "webhook → https://hooks.example.com/amba",
        "would": { "webhook_delivery": { "matched_via": "exact" } }
      }
    ],
    "evaluated_surfaces": ["xp", "currency_grant", "feed", "streak", "webhook"]
  }
}

Each matched entry may carry a note when the effect matched but a per-user gate (daily limit, cooldown, balance cap) would currently suppress or reduce it. When nothing matches, matched is empty and unmatched_reason_samples explains why.

Errors

  • 400 INVALID_BODY — body is not JSON or omits the event object.
  • 400 INVALID_EVENT_NAMEevent.name is missing or empty.
  • 400 INVALID_PROPERTIES / 400 INVALID_USER_ID — wrong type for those fields.
  • 500 EXPLAIN_FAILED.

Curl:

curl -X POST '${BASE_URL}/client/events/explain' \
  -H 'X-Api-Key: ${CLIENT_API_KEY}' \
  -H 'Content-Type: application/json' \
  -d '{"event":{"name":"lesson_completed","properties":{"score":90}}}'

On this page