Amba

XP

Manage XP rules and inspect per-user XP balances.

XP rules map engagement events to XP awards; they're evaluated automatically on every POST /client/events write. This module also exposes read endpoints for per-user XP balances and two manual adjustment endpoints (/xp/grant, /xp/deduct) for support corrections and promos.

Endpoints

MethodPathDescription
POST/admin/projects/:projectId/xpCreate an XP rule.
GET/admin/projects/:projectId/xpList XP rules.
GET/admin/projects/:projectId/xp/configRead the project's level curve.
PATCH/admin/projects/:projectId/xp/configSet xp_per_level and/or level_thresholds.
PATCH/admin/projects/:projectId/xp/:ruleIdPartial update.
DELETE/admin/projects/:projectId/xp/:ruleIdDelete a rule.
POST/admin/projects/:projectId/xp/grantManually add XP to a user (support / promo).
POST/admin/projects/:projectId/xp/deductManually subtract XP from a user (inverse of grant).
GET/admin/projects/:projectId/xp/users/:userIdFetch a single user's XP.
GET/admin/projects/:projectId/xp/usersPaginated leaderboard-style list of users by total XP.

POST /admin/projects/:projectId/xp

Request

FieldTypeRequiredDefault
namestringyes
event_namestringyes
xp_amountnumberyes
max_per_daynumber | nullnonull
cooldown_secondsnumberno0

Response 201

{
  "data": {
    "id": "…",
    "name": "…",
    "event_name": "workout_completed",
    "xp_amount": 10,
    "max_per_day": null,
    "cooldown_seconds": 0
  }
}

Try it:

POST/admin/projects/%7B%7BprojectId%7D%7D/xp
developer auth
curl -X POST 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/xp'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/admin/projects/{projectId}/xp' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

GET /admin/projects/:projectId/xp

{ "data": [{ "id": "…", "name": "…", "event_name": "…", "xp_amount": 10 }] }

Try it:

GET/admin/projects/%7B%7BprojectId%7D%7D/xp
developer auth
curl -X GET 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/xp'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X GET '${BASE_URL}/admin/projects/{projectId}/xp' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'

GET /admin/projects/:projectId/xp/config

The project's effective level curve.

Response 200

{ "data": { "xp_per_level": 1000, "level_thresholds": null } }

PATCH /admin/projects/:projectId/xp/config

Set the level curve. Provide xp_per_level and/or level_thresholds (at least one). A partial patch preserves the untouched field.

Request

FieldTypeNotes
xp_per_levelintegerFlat curve: level = floor(total_xp / xp_per_level) + 1. Must be a positive integer.
level_thresholdsinteger[] / nullExplicit cumulative-XP boundaries, strictly increasing. Wins over xp_per_level. null clears it.

Response 200

{ "data": { "xp_per_level": 1000, "level_thresholds": [100, 300, 700, 1500], "persisted": true } }

persisted is true once the curve is durably stored, false on a project provisioned before the feature shipped (the effective default is returned but not saved). Setting a curve does not retroactively recompute existing user levels — they rebase on each user's next XP write.

Errors

  • 400 VALIDATION_ERROR — neither field supplied.
  • 400 INVALID_XP_PER_LEVELxp_per_level is not a positive integer.
  • 400 INVALID_LEVEL_THRESHOLDS — not a strictly-increasing array of positive integers (or null).
  • 500 UPDATE_FAILED.

Try it:

PATCH/admin/projects/%7B%7BprojectId%7D%7D/xp/config
developer auth
curl -X PATCH 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/xp/config' \
  -H 'Content-Type: application/json' \
  -d '{
  "xp_per_level": 500
}'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X PATCH '${BASE_URL}/admin/projects/{projectId}/xp/config' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{ "level_thresholds": [100, 300, 700, 1500] }'

PATCH /admin/projects/:projectId/xp/:ruleId

Allowed fields: name, event_name, xp_amount, max_per_day, cooldown_seconds, is_active.

Errors

  • 404 NOT_FOUND.
  • 500 UPDATE_FAILED.

Try it:

PATCH/admin/projects/%7B%7BprojectId%7D%7D/xp/%7B%7BruleId%7D%7D
developer auth
curl -X PATCH 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/xp/%7B%7BruleId%7D%7D'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X PATCH '${BASE_URL}/admin/projects/{projectId}/xp/{ruleId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

DELETE /admin/projects/:projectId/xp/:ruleId

{ "data": { "deleted": true } }

Try it:

DELETE/admin/projects/%7B%7BprojectId%7D%7D/xp/%7B%7BruleId%7D%7D
developer auth
curl -X DELETE 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/xp/%7B%7BruleId%7D%7D'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X DELETE '${BASE_URL}/admin/projects/{projectId}/xp/{ruleId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'

POST /admin/projects/:projectId/xp/grant

Manually add XP to a user — for rewards, promos, or support corrections. Writes a signed (positive) xp_ledger row and bumps the user's total_xp. The user's level is recomputed from the new total so their displayed level stays consistent.

This is a manual adjustment, not an event. It deliberately does not fire the rule-driven side-effects an automatic award would: no level-up engagement events are emitted and achievement criteria are not re-evaluated. The ledger row is written with xp_rule_id = null and a reason of admin_grant (override with reason) so the adjustment is auditable and distinguishable from rule-driven XP. To also award a badge, call /achievements/:id/grant explicitly; to top up a currency, use /currencies/grant.

Request (AdjustXpInput)

FieldTypeRequiredDefaultDescription
app_user_iduuidyesUser to grant XP to. A first-time grant creates the user's XP row.
amountnumberyesPositive integer magnitude of XP to add.
reasonstringnoadmin_grantStored on the ledger row for the audit trail.

Response 201

{
  "data": {
    "total_xp": 1284,
    "level": 13,
    "transaction": {
      "id": "…",
      "app_user_id": "…",
      "xp_rule_id": null,
      "amount": 50,
      "reason": "admin_grant",
      "created_at": "…"
    }
  }
}

Errors

  • 400 VALIDATION_ERRORapp_user_id missing.
  • 400 INVALID_AMOUNTamount isn't a positive integer (fractional amounts are rejected, not truncated).
  • 400 INVALID_IDapp_user_id isn't a UUID.
  • 500 XP_ADJUST_FAILED.

Try it:

POST/admin/projects/%7B%7BprojectId%7D%7D/xp/grant
developer auth
curl -X POST 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/xp/grant'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/admin/projects/{projectId}/xp/grant' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{ "app_user_id": "…", "amount": 50, "reason": "support_correction" }'

POST /admin/projects/:projectId/xp/deduct

The inverse of grant: subtract XP from a user's total. Atomic — returns 400 INSUFFICIENT_XP if the deduction would drive the total below zero (no partial deduct). Like grant, this is a manual adjustment that does not replay rule side-effects; the ledger records the magnitude as a negative delta (so SUM(amount) over a user's ledger reconstructs total_xp) with xp_rule_id = null and a reason of admin_deduct.

Request (AdjustXpInput)

FieldTypeRequiredDefaultDescription
app_user_iduuidyesUser to deduct XP from.
amountnumberyesPositive integer magnitude of XP to subtract.
reasonstringnoadmin_deductStored on the ledger row for the audit trail.

Response 201

{
  "data": {
    "total_xp": 1234,
    "level": 12,
    "transaction": {
      "id": "…",
      "app_user_id": "…",
      "xp_rule_id": null,
      "amount": -50,
      "reason": "admin_deduct",
      "created_at": "…"
    }
  }
}

Errors

  • 400 VALIDATION_ERRORapp_user_id missing.
  • 400 INVALID_AMOUNTamount isn't a positive integer.
  • 400 INVALID_IDapp_user_id isn't a UUID.
  • 400 INSUFFICIENT_XP — the deduction would drive total_xp below zero. The message includes the requested amount and the user's current total.
  • 500 XP_ADJUST_FAILED.

Try it:

POST/admin/projects/%7B%7BprojectId%7D%7D/xp/deduct
developer auth
curl -X POST 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/xp/deduct'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/admin/projects/{projectId}/xp/deduct' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{ "app_user_id": "…", "amount": 50, "reason": "reversed_promo" }'

GET /admin/projects/:projectId/xp/users/:userId

Response 200

{ "data": { "app_user_id": "…", "total_xp": 1234, "level": 12, "updated_at": "…" } }

Errors

  • 404 NOT_FOUND — user has no XP balance yet (no XP-awarding events have fired).

Try it:

GET/admin/projects/%7B%7BprojectId%7D%7D/xp/users/%7B%7BuserId%7D%7D
developer auth
curl -X GET 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/xp/users/%7B%7BuserId%7D%7D'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X GET '${BASE_URL}/admin/projects/{projectId}/xp/users/{userId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'

GET /admin/projects/:projectId/xp/users

Paginated list ordered by total_xp DESC.

Query

ParamDefault
limit50
offset0

Response 200

{
  "data": [
    {
      "app_user_id": "…",
      "total_xp": 1234,
      "level": 12,
      "app_users": { "display_name": "…", "avatar_url": "…" }
    }
  ],
  "total": 1234,
  "offset": 0,
  "limit": 50
}

Try it:

GET/admin/projects/%7B%7BprojectId%7D%7D/xp/users
developer auth
curl -X GET 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/xp/users'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X GET '${BASE_URL}/admin/projects/{projectId}/xp/users' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'