Assistiv Docs

[ INTEGRATION ]

End-user wallet (display ledger)

apiv0.1.0sdk@assistiv/sdk@0.2.0

The display ledger is an opt-in product-billing surface that runs in parallel to the USD ledger on end_user_budgets. Platforms configure per-rule deductions (per-call, per-tool, per-USD) and admin endpoints to set, top up, adjust, or disable each end user's wallet. End users see a translated balance via GET /v1/me/budget.

Two ledgers, one row

Every end user has a single row in end_user_budgets with two ledgers:

  • USD ledger (max_usd, used_usd): mandatory cost control. Debited by the actual provider cost on every inference call. Hidden from end users.
  • Display ledger (max_display, used_display): opt-in product billing. Debited by the rules engine you configure on platforms.settings.end_user_wallet and by these admin endpoints. NULL max_display = disabled for that user.

The two ledgers serve different purposes and are allowed to drift. Either exhausting fires an HTTP 402 on inference. Configure the wallet via the dashboard settings (or PATCH /v1/platforms/{pid} with { settings: { end_user_wallet: ... } }).

GET/v1/platforms/{platformId}/end-users/{endUserId}/wallet

Read both ledgers + the platform's active rules for this user.

Auth: Platform key (sk-plat_*) or Supabase session. End-user keys get 403 — see /v1/me/budget for the end-user-facing view.

display_ledger is null when max_display IS NULL on the budget row (display ledger not yet initialized for this end user).

bash
curl https://api.assistiv.ai/v1/platforms/{pid}/end-users/{euid}/wallet \
  -H "Authorization: Bearer sk-plat_your_key"
json
{
  "end_user_id": "uuid",
  "budget_id": "uuid",
  "usd_ledger": {
    "max_usd": 5.00,
    "used_usd": 1.00,
    "remaining_usd": 4.00,
    "is_active": true,
    "is_suspended": false
  },
  "display_ledger": {
    "unit": "credits",
    "max": 100,
    "used": 20,
    "remaining": 80,
    "active_rules": [
      { "trigger": "inference_call", "amount": 1 },
      { "trigger": "tool_call", "amount": 5 }
    ]
  }
}
POST/v1/platforms/{platformId}/end-users/{endUserId}/wallet

Initialize (max_display was NULL) or overwrite max_display.

On first call (initialization), writes a type=opening audit row and resets used_display to 0. Subsequent calls write a type=topup row with the signed delta and preserve used_display.

Idempotency: Pass an Idempotency-Key header to make retries safe. Same key + same body → replay; same key + different body → 409.

bash
curl -X POST https://api.assistiv.ai/v1/platforms/{pid}/end-users/{euid}/wallet \
  -H "Authorization: Bearer sk-plat_your_key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: \$(uuidgen)" \
  -d '{"max_display": 100, "reason": "initial provisioning"}'
json
{
  "budget_id": "uuid",
  "max_display": 100,
  "used_display": 0,
  "idempotent_replay": false,
  "no_changes": false
}
POST/v1/platforms/{platformId}/end-users/{endUserId}/wallet/topup

Add to max_display. Used_display is preserved.

Fails with 409 display_ledger_not_initialized if max_display IS NULL — call POST /wallet first to initialize.

bash
curl -X POST https://api.assistiv.ai/v1/platforms/{pid}/end-users/{euid}/wallet/topup \
  -H "Authorization: Bearer sk-plat_your_key" \
  -H "Content-Type: application/json" \
  -d '{"amount_display": 50, "reason": "monthly refill"}'
POST/v1/platforms/{platformId}/end-users/{endUserId}/wallet/adjust

Manual ± to balance. Positive delta = credit; negative = debit.

delta operates on balance (max - used). The RPC clamps so used_display stays in [0, max_display]. When clamping happens, the response reports both the requested and applied delta.

reason is required and audit-logged. Empty / whitespace-only reasons return 422.

bash
curl -X POST https://api.assistiv.ai/v1/platforms/{pid}/end-users/{euid}/wallet/adjust \
  -H "Authorization: Bearer sk-plat_your_key" \
  -H "Content-Type: application/json" \
  -d '{"delta": 50, "reason": "promo bonus"}'
json
{
  "budget_id": "uuid",
  "max_display": 100,
  "used_display": 30,
  "requested_delta": -200,
  "applied_delta": -80,
  "clamped": true,
  "idempotent_replay": false
}
DELETE/v1/platforms/{platformId}/end-users/{endUserId}/wallet

Soft-disable the display ledger. USD ledger untouched.

NULLs out max_display, resets used_display to 0, and writes one type=adjustment audit row with system reason wallet_disabled. Idempotent — a re-DELETE is a no-op returning the current (NULL) state.

The end user's /v1/me/budget immediately starts returning 404 (per-user gate). USD billing continues normally.

bash
curl -X DELETE https://api.assistiv.ai/v1/platforms/{pid}/end-users/{euid}/wallet \
  -H "Authorization: Bearer sk-plat_your_key"

Rules engine

Rules live on platforms.settings.end_user_wallet.rules and apply to every inference call. Each rule is one of:

  • { trigger: "inference_call", amount: N } — flat N per call.
  • { trigger: "tool_call", amount: N } — flat N per MCP or skill tool invocation in the call.
  • { trigger: "usd_spent", amount_per_usd: N } — N × actual provider cost in USD.

Rules are additive — every matching rule contributes to the post-call used_display delta. Max 8 rules per platform; no duplicate triggers.