Skip to main content

Wallets

4 API calls in this section.

Endpoints for managing user wallet operations including topup, transfers, and transaction history.

Manual Customer Wallet Transaction​

Platform & Access / Users / Wallets
POST/users/:userId/wallet/manual-transaction

Creates a manual wallet credit or debit for an individual or business customer in the authenticated tenant. Requires a system administrator, the exact tenant_admin role, or an explicit manage:wallets permission. Finance must be granted manage:wallets; manage:tenant alone does not authorize adjustments, including for dispatchers. Wallet ownership alone does not grant access. Staff roles and drivers cannot be targeted by this customer endpoint.

type is credit or debit; amount is positive, below 1,000,000,000,000,000, with at most four decimal places; reason is required, trimmed, and at most 500 characters. Optional reference (at most 200 characters) and notes (at most 500 characters) must be strings or null. They are trimmed; omitted, null, empty, and whitespace-only values become null. Length limits apply after trimming. Actor identity and tenant come from authenticated context. Currency comes from tenant settings.

One database commit locks the wallet, checks the customer minimum available balance (draft reservations are already reflected in wallet balance), and writes the ledger record and audit event. The limit comes from customer_wallet_minimum_balance.settings.minimum_balance; wallet_minimum_balance is for drivers. Credits can restore a wallet already below its limit. Credit ledger rows use type=earning and positive amount; debit rows use type=payment and negative amount. This endpoint response keeps the requested positive amount and credit/debit type, and returns saved reference and notes as string or null. Both optional fields are stored in the existing transaction metadata and audit event. Nonempty notes append - {notes} to the description; without notes, existing description behavior is unchanged.

Idempotency-Key is a required UUID. Repeating the same key and payload returns the original transaction and balance with replayed=true, without another ledger/audit write. Reusing a key with a different tenant, customer, actor, type, amount, reason, normalized reference, or normalized notes returns HTTP 409 idempotency_conflict.

Frontend integration

display Credit/Debit, positive amount, required reason, optional Reference (200 characters), and optional Notes (500 characters), then confirm before POST. Include the optional values in both the confirmation and payload; preserve the same complete payload for an uncertain retry. Generate one UUID per confirmed adjustment and retain it for uncertain retries. After HTTP 200, refresh the existing wallet summary and ledger; stay on the customer's Wallet tab. Read ledger manual_transaction.type, .reason, .reference, .notes, and .actor.{id,name} for action, reason, and administrator. Do not infer adjustments from description text. Show the button to system administrators, users with the exact tenant_admin role, or users granted manage:wallets; the backend independently enforces the same rule.

Errors use localized message and stable code: 400 reference_too_long, notes_too_long, invalid_amount, invalid_transaction_type, reason_required, reason_too_long, idempotency_key_required, tenant_id_required, invalid_request_body, or insufficient_wallet_balance; 403 insufficient_permissions; 404 customer_not_found or wallet_not_found; 409 idempotency_conflict; 500 internal_server_error. Invalid optional-field types or lengths use the existing HTTP 400 validation response with localized message and details entries identifying reference or notes. Invalid user, tenant, or idempotency UUIDs use the same validation format. Allow correction on 400, deny the action on 403, and start a fresh adjustment on 409.

Path parameters

ParameterTypeDescription
userIdRequiredstring

Customer user UUID, not tenant_users ID or driver ID.

Example: {{customer_user_id}}

Query parameters

ParameterTypeDescription
tenant_idstring

Selected tenant; authentication must resolve this tenant.

Example: {{tenant_id}}

languagestring

Response language: en, ar, fa, or ku.

Example: en

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Idempotency-KeyRequiredstring

Required UUID generated once per confirmed adjustment; reuse for retries.

Example: {{wallet_adjustment_key}}

Body parameters

ParameterTypeDescription
typestring—
amountnumber—
reasonstring—
referencestring—
notesstring—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/users/${USERID}/wallet/manual-transaction?tenant_id=${TENANT_ID}&language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --header "Idempotency-Key: ${WALLET_ADJUSTMENT_KEY}" \5  --data-raw '{6  "type": "credit",7  "amount": 10000,8  "reason": "Customer cash payment received",9  "reference": "CASH-2026-001",10  "notes": "Received at the front desk."11}'

Response examples

200Success
Response body
json
1{2  "success": true,3  "message": "Customer wallet transaction saved successfully.",4  "transaction": {5    "transaction_id": "550e8400-e29b-41d4-a716-446655440000",6    "new_balance": 15000,7    "amount": 10000,8    "type": "credit",9    "reason": "Customer cash payment received",10    "created_at": "2026-10-14T12:00:00Z",11    "actor": {12      "id": "550e8400-e29b-41d4-a716-446655440001",13      "name": "Tenant Admin"14    },15    "replayed": false,16    "reference": "CASH-2026-001",17    "notes": "Received at the front desk."18  }19}

Top-up Wallet​

Platform & Access / Users / Wallets
POST/users/:userId/wallet/topup

Top-up User Wallet

Adds funds to a user's wallet. The wallet is automatically identified by the user ID.

  • Requires Bearer Token authentication

  • User can only top-up their own wallet

  • Tenant admins can top-up any user's wallet

Endpoint

POST /users/{userId}/wallet/topup

Features

  • Automatic wallet lookup by user ID

  • Currency validation

  • Transaction record creation

  • Balance update via database trigger

  • Tenant isolation

Path parameters

ParameterTypeDescription
userIdRequiredstring

UUID of the user whose wallet to top up

Example: {{user_id}}

Query parameters

ParameterTypeDescription
languageOptionalstring

Language code for response localization (Optional, default: en)

Example: en

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
amountnumber—
currencystring—
descriptionstring—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/users/${USERID}/wallet/topup?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "amount": 100,6  "currency": "USD",7  "description": "Adding funds to wallet"8}'
Other request examples (1)
Request example 1
text
1{2  "amount": 100.00,     // required - Amount to add (min: 0.01)3  "currency": "USD",   // optional - Currency code (default: USD)4  "description": "Adding funds to wallet" // optional - Description5}

Response examples

200OK
Response body
json
1{2  "success": true,3  "wallet": {4    "transaction_id": "550e8400-e29b-41d4-a716-446655440000",5    "new_balance": 1000,6    "amount": 100,7    "currency": "USD",8    "timestamp": "2023-01-01T00:00:00Z"9  }10}

Transfer Funds​

Platform & Access / Users / Wallets
POST/users/:userId/wallet/transfer

Transfer Funds Between Users

Transfers funds from one user's wallet to another user within the same tenant.

  • Requires Bearer Token authentication

  • User can only transfer from their own wallet

Endpoint

POST /users/{userId}/wallet/transfer

Features

  • Automatic sender wallet lookup by user ID

  • Recipient lookup by email or user_id

  • Balance validation

  • Atomic transaction

  • Tenant isolation

  • Transfer history tracking

Path parameters

ParameterTypeDescription
userIdRequiredstring

UUID of the sender user

Example: {{user_id}}

Query parameters

ParameterTypeDescription
languageOptionalstring

Language code for response localization (Optional, default: en)

Example: en

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
amountnumber—
recipient_identifierstring—
identifier_typestring—
descriptionstring—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/users/${USERID}/wallet/transfer?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "amount": 50,6  "recipient_identifier": "[email protected]",7  "identifier_type": "email",8  "description": "Payment for services"9}'

Response examples

200OK
Response body
json
1{2  "success": true,3  "transfer": {4    "transfer_id": "550e8400-e29b-41d4-a716-446655440000",5    "new_balance": 950,6    "amount": 50,7    "currency": "USD",8    "timestamp": "2023-01-01T00:00:00Z"9  }10}

List Wallet Transactions​

Platform & Access / Users / Wallets
GET/users/:userId/wallet/transactions

List User Wallet Transactions

Retrieves a paginated list of wallet transactions for a specific user with filtering options.

  • Requires Bearer Token authentication

  • User can only access their own transactions

  • System administrators or staff with manage:tenant or manage:wallets can access wallets of users belonging to their authenticated tenant

Endpoint

GET /users/{userId}/wallet/transactions

Summary Rules

  • summary.total_credited is the sum of positive wallet_transactions.amount values over the full filtered result set.

  • summary.total_debited is the sum of absolute negative wallet_transactions.amount values over the full filtered result set.

  • Summary totals respect type, search, from_date, to_date, source, and actor_name, but never page or limit.

  • Cash/COD collections that do not create wallet transaction rows are not included in wallet credited/debited totals.

Features

  • Automatic wallet lookup by user ID

  • Comprehensive pagination metadata

  • Type and date range filtering

  • Search on amount and description

  • Transfer details enhancement

  • Chronological ordering

  • Wallet balance and filtered wallet summary included

Frontend integration contract

Use summary.total_credited and summary.total_debited for wallet stat cards. Do not reduce the current page of transactions to calculate these totals.

Manual customer adjustments

New manual entries include manual_transaction: {type: "credit"|"debit", reason: string, reference: string | null, notes: string | null, actor: {id: string, name: string}}. These values are saved with the transaction; no user lookup is required. Reference and Notes are null for older manual entries that did not store them. Display the saved values rather than parsing the description. The manual_transaction field remains absent for non-manual entries. Manual credit rows retain ledger type=earning and debit rows retain type=payment; signed amount remains the source for summary totals. Include tenant_id in staff requests so authentication resolves the intended tenant.

Source and Recorded By columns

Every row includes source: string | null and recorded_by: { id: string | null, name: string | null } | null, projected only from saved metadata. Source is the literal saved source (for example customer_manual_adjustment or delivery_fee_refund), not a Manual/Other category. Recorded By uses the saved actor_user_id and actor_name; missing or empty values are null. No user lookup is performed, and older rows without a saved actor name must not be given an invented name. The existing manual transaction object remains available.

Use a dropdown for Source, populated from top-level source_options: string[]. Options are the distinct nonempty saved sources from the full wallet history, sorted and independent of all filters and pagination, including when the current filtered result is empty. Display readable localized labels with a humanized fallback, but send the exact selected raw value as source. Source matches the saved value exactly and case-sensitively; All means omit source. Recorded By remains a text filter sent as actor_name, with case-insensitive literal contains matching. Both filter inputs are trimmed; empty inputs are ignored. Asterisk, percent, underscore, backslash and other pattern characters are ordinary text. All active filters are combined with AND before pagination and are applied identically to total, summary.total_credited, and summary.total_debited; current wallet balance remains unfiltered. Reset the page to 1 when changing column filters.

Example

GET /users/:userId/wallet/transactions?tenant_id={{tenant_id}}&source=customer_manual_adjustment&actor_name=Amit&page=1&limit=20. Authentication, access-denied responses and other error behavior are unchanged.

Path parameters

ParameterTypeDescription
userIdRequiredstring

UUID of the user whose transactions to retrieve

Example: {{user_id}}

Query parameters

ParameterTypeDescription
sourceOptionalstring

Exact, case-sensitive saved source value selected from source_options; omit for All. (string, optional): Exact, case-sensitive match on the selected saved source value

actor_nameOptionalstring

Trimmed, case-insensitive literal contains match on saved actor name; rows without a name do not match. (string, optional): Case-insensitive literal contains match on the saved recorded-by name

pageOptionalstring

Page number for pagination (default: 1) (number, optional): Page number (default: 1)

Example: 1

limitOptionalstring

Number of items per page (default: 20, max: 100) (number, optional): Items per page (default: 20, max: 100)

Example: 20

typeOptionalstring

Filter by transaction type: topup, transfer, payment, earning (string, optional): Filter by type (topup, transfer, payment, earning)

searchOptionalstring

Search by description (partial match) or amount (exact match). If numeric, matches amount; always matches description. (string, optional): Search by description (partial match) or amount (exact match)

from_dateOptionalstring

Filter from date (ISO format: 2024-01-01T00:00:00Z) (string, optional): Filter from date (ISO format)

to_dateOptionalstring

Filter to date (ISO format: 2024-12-31T23:59:59Z) (string, optional): Filter to date (ISO format)

languageOptionalstring

Language code for response localization (Optional, default: en)

Example: en

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/users/${USERID}/wallet/transactions?page=1&limit=20&type=&search=&source=&actor_name=&from_date=&to_date=&language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200Success - Source and Recorded By filters
Response body
json
1{2  "success": true,3  "message": "Wallet transactions retrieved successfully",4  "wallet": {5    "id": "550e8400-e29b-41d4-a716-446655440000",6    "balance": 15000,7    "reserved": 08  },9  "summary": {10    "current_balance": 15000,11    "total_credited": 10000,12    "total_debited": 013  },14  "transactions": [15    {16      "id": "550e8400-e29b-41d4-a716-446655440001",17      "type": "earning",18      "amount": 10000,19      "description": "Manual credit: Customer payment - Received at the front desk.",20      "reference_id": null,21      "reference_type": null,22      "created_at": "2026-10-14T12:00:00Z",23      "delivery": null,24      "source": "customer_manual_adjustment",25      "recorded_by": {26        "id": "550e8400-e29b-41d4-a716-446655440002",27        "name": "Amit"28      },29      "manual_transaction": {30        "type": "credit",31        "reason": "Customer payment",32        "actor": {33          "id": "550e8400-e29b-41d4-a716-446655440002",34          "name": "Amit"35        },36        "reference": "CASH-2026-001",37        "notes": "Received at the front desk."38      }39    }40  ],41  "page": 1,42  "limit": 20,43  "total": 1,44  "source_options": [45    "customer_manual_adjustment",46    "delivery_fee_refund",47    "wallet_delivery_fee"48  ]49}