Amba

Currencies

Virtual currency definitions, admin grants, per-user transaction history.

Currencies are named tokens (code like "gems") with optional cap (max_balance) and auto-recharge rules. user_balances holds per-user state. Grants are performed atomically under a FOR UPDATE row lock with a pre-flight zero-row insert so first-time grants can't race.

Endpoints

MethodPathDescription
POST/admin/projects/:projectId/currenciesCreate a currency definition.
GET/admin/projects/:projectId/currenciesList currency definitions.
PATCH/admin/projects/:projectId/currencies/:currencyIdPartial update.
DELETE/admin/projects/:projectId/currencies/:currencyIdDelete.
POST/admin/projects/:projectId/currencies/grantGrant currency to a user (admin source).
POST/admin/projects/:projectId/currencies/spendDebit currency from a user (admin source).
GET/admin/projects/:projectId/currencies/transactions/:userIdPaginated transaction history for a user.

POST /admin/projects/:projectId/currencies

Request

FieldTypeRequiredDefault
codestringyes
namestringyes
descriptionstringnonull
is_premiumbooleannofalse
initial_balancenumberno0
max_balancenumbernonull (uncapped)
auto_recharge_amountnumbernonull
auto_recharge_interval_hoursnumbernonull

When initial_balance > 0, that starting amount is spendable. On a user's first grant or spend of this currency, Amba lazily credits the starting balance into the ledger as a system transaction before applying the operation, so the balance read and the summed transaction history stay in agreement and the starting amount can be spent immediately. A currency with initial_balance of 0 credits nothing — the user starts empty.

Response 201

{
  "data": {
    "id": "…",
    "code": "gems",
    "name": "Gems",
    "is_premium": false,
    "initial_balance": 0,
    "max_balance": 9999
  }
}

Try it:

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

Curl:

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

GET /admin/projects/:projectId/currencies

{ "data": [{ "code": "gems", "name": "Gems", "is_premium": false }] }

Try it:

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

Curl:

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

PATCH /admin/projects/:projectId/currencies/:currencyId

Allowed fields: name, description, is_premium, initial_balance, max_balance, auto_recharge_amount, auto_recharge_interval_hours.

Try it:

PATCH/admin/projects/%7B%7BprojectId%7D%7D/currencies/%7B%7BcurrencyId%7D%7D
developer auth
curl -X PATCH 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/currencies/%7B%7BcurrencyId%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}/currencies/{currencyId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

DELETE /admin/projects/:projectId/currencies/:currencyId

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

Try it:

DELETE/admin/projects/%7B%7BprojectId%7D%7D/currencies/%7B%7BcurrencyId%7D%7D
developer auth
curl -X DELETE 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/currencies/%7B%7BcurrencyId%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}/currencies/{currencyId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'

POST /admin/projects/:projectId/currencies/grant

Grant currency to a user. Clamped to max_balance if set; a fully-clamped grant still returns 201 with a zero-delta currency_transactions row for auditability.

Request (GrantCurrencyInput)

FieldTypeRequiredDescription
app_user_iduuidyesRecipient.
currency_codestringyesMust exist in currency_definitions.
amountnumberyesMust be finite and > 0.
reasonstringnoFree-form reference id stored on the transaction.
idempotency_keystringno≤255 chars. Repeating the same key for this user + currency replays the original transaction without re-applying the balance change. See Idempotency.

Response 201

{
  "data": {
    "balance": 150,
    "idempotent_replay": false,
    "transaction": {
      "id": "…",
      "app_user_id": "…",
      "currency_code": "gems",
      "amount": 50,
      "balance_after": 150,
      "transaction_type": "grant",
      "source": "admin",
      "reference_id": null,
      "created_at": "…"
    }
  }
}

Response 200 (idempotent replay)

A repeat with a previously-seen idempotency_key (same user + currency) returns the original transaction with "idempotent_replay": true and HTTP 200 — the balance is not changed. See Idempotency.

Errors

  • 400 INVALID_AMOUNT — amount isn't a positive finite number.
  • 404 CURRENCY_NOT_FOUND — unknown currency_code.
  • 500 GRANT_FAILED.

Try it:

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

Curl:

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

POST /admin/projects/:projectId/currencies/spend

Debit currency from a user. Atomic — returns 400 INSUFFICIENT_FUNDS if the balance is too low. No partial debit; the request either fully succeeds or fully fails.

The ledger records the spend as a negative amount (signed delta), so SUM(amount) across a user's transactions reconstructs the current balance.

Request (SpendCurrencyInput)

FieldTypeRequiredDescription
app_user_iduuidyesUser to debit.
currency_codestringyesMust exist in currency_definitions.
amountnumberyesPositive, finite. The endpoint records it as a negative delta on the ledger.
reasonstringnoFree-form reference id stored on the transaction.
idempotency_keystringno≤255 chars. Repeating the same key for this user + currency replays the original transaction without re-debiting. A spend that failed with INSUFFICIENT_FUNDS stores no key, so that key can be retried. See Idempotency.

Response 201

{
  "data": {
    "balance": 70,
    "idempotent_replay": false,
    "transaction": {
      "id": "…",
      "app_user_id": "…",
      "currency_code": "gems",
      "amount": -30,
      "balance_after": 70,
      "transaction_type": "spend",
      "source": "admin",
      "reference_id": "ai_generation",
      "created_at": "…"
    }
  }
}

Response 200 (idempotent replay)

A repeat with a previously-seen idempotency_key (same user + currency) returns the original transaction with "idempotent_replay": true and HTTP 200 — the balance is not changed. See Idempotency.

Errors

  • 400 INVALID_AMOUNT — amount isn't a positive finite number.
  • 400 INSUFFICIENT_FUNDS — current balance is below amount. The error message includes both values. No key is stored, so the same idempotency_key may be retried after the user tops up.
  • 404 CURRENCY_NOT_FOUND — unknown currency_code.
  • 500 SPEND_FAILED.

Try it:

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

Curl:

curl -X POST '${BASE_URL}/admin/projects/{projectId}/currencies/spend' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{
        "app_user_id": "…",
        "currency_code": "gems",
        "amount": 30,
        "reason": "ai_generation"
      }'

Idempotency

Both grant and spend accept an optional idempotency_key (string, ≤255 chars). Sending the same key again for the same app_user_id + currency_code makes the operation exactly-once:

  • The first request applies the balance change and responds 201 with "idempotent_replay": false.
  • Any later request with the same key returns the original transaction unchanged, responds 200 with "idempotent_replay": true, and does not re-apply the delta.

The key is permanent — it's anchored to the ledger row it created, with no expiry window. The caller controls the dedup window through what they encode into the key. To cap a reward at "one grant per share per day," embed the date: idempotency_key: "share:{userId}:{yyyy-mm-dd}". The first call that day lands; the rest are no-op replays; the next day's key is different so the grant lands again — no guard collection of your own required.

A spend that fails with INSUFFICIENT_FUNDS stores no key (no ledger row was written), so the same key can be retried after the user tops up.

The same idempotency_key field is available on the amba_currencies_grant and amba_currencies_spend MCP tools, with identical semantics.

GET /admin/projects/:projectId/currencies/transactions/:userId

Paginated transaction history for a single user, most recent first.

Query

ParamDefault
limit50
offset0

Response 200

{
  "data": [
    {
      "id": "…",
      "currency_code": "gems",
      "amount": 50,
      "balance_after": 150,
      "transaction_type": "grant"
    }
  ],
  "total": 42,
  "offset": 0,
  "limit": 50
}

Try it:

GET/admin/projects/%7B%7BprojectId%7D%7D/currencies/transactions/%7B%7BuserId%7D%7D
developer auth
curl -X GET 'https://api.amba.dev/v1/admin/projects/%7B%7BprojectId%7D%7D/currencies/transactions/%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}/currencies/transactions/{userId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'