Skip to main content

Users

This section contains 1 subgroups.

Get Current User​

Platform & Access / Users
GET/users/me

Retrieves the current authenticated user. Returns user with warehouse_staff, restaurant_staff (object or null), panel_type, tenant_info, role, notification preferences, market/restaurant portal context flags, and login_country derived from the phone dial code when the user has a phone number. An inactive application user remains inactive across sign-in and other Supabase Auth updates; authentication activity never reactivates an inactive account. Inactive users receive HTTP 403 until an authorized user explicitly changes their application status to active.

Pending invited accounts activate when their confirmed invitation is completed with a first password. Inactive accounts remain inactive.

Query parameters

ParameterTypeDescription
languageOptionalstring

Language code (Optional)

Example: en

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/users/me?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200Success
Response body
json
1{2  "success": true,3  "message": "Users retrieved successfully",4  "user": {5    "id": "user-uuid",6    "email": "[email protected]",7    "first_name": "John",8    "last_name": "Doe",9    "status": "active",10    "notifications_enabled": true,11    "is_system_admin": false,12    "is_staff": true,13    "is_market_user": true,14    "market_id": "market-uuid",15    "is_restaurant_user": false,16    "restaurant_location_id": null,17    "login_country": {18      "code": "IN",19      "name": "India",20      "dial_code": "+91"21    },22    "role": {23      "id": "role-uuid",24      "name": "restaurant_staff",25      "scope": "tenant"26    },27    "panel_type": "dispatcher",28    "warehouse_staff": [],29    "restaurant_staff": {30      "id": "staff-uuid",31      "location_id": "location-uuid",32      "staff_role": "cashier",33      "location": {34        "id": "location-uuid",35        "name": {},36        "code": 1000,37        "tenant_id": "tenant-uuid",38        "active": true39      }40    }41  }42}

Update My Context​

Platform & Access / Users
POST/users/me/context

Stores the authenticated user's market or restaurant context after portal login.

Query parameters

ParameterTypeDescription
languageOptionalstring

Language code (Optional)

Example: en

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
portalstring

market or restaurant

market_idstring

required when portal=market

restaurant_location_idstring

required when portal=restaurant

Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/users/me/context?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "portal": "market",6  "market_id": "{{market_id}}"7}'

Replace {{placeholders}} with your own values before sending.

Response examples

200Success - Market
Response body
json
1{2  "success": true,3  "message": "User Updated Successfully",4  "data": {5    "id": "user-uuid",6    "is_market_user": true,7    "market_id": "market-uuid",8    "is_restaurant_user": false,9    "restaurant_location_id": null10  }11}

Get List Users​

Platform & Access / Users
GET/users

Lists users with pagination, search, sorting, advanced column filters, and tenant KPI stats. Existing top-level filters continue to work.

  • When both top-level role_id and column_filters.role_id are provided, both filters must match.

Frontend integration

send the selected role UUID either as top-level role_id or as column_filters.role_id. Continue using column_filters.role only for role names. No navigation behavior changes. Invalid filter types return HTTP 400.

Response

includes stats (totalUsers, activeUsers, pendingUsers, recentlyJoined) plus the existing statusCounts block.

Account managers

each returned user includes is_account_manager (tenant designation, independent of role permissions) and account_manager: null or { "id": "user UUID", "name": "manager full name", "is_account_manager": true }. Existing assignments remain visible after their manager is unmarked, with account_manager.is_account_manager: false. Names come from one batched lookup of current user records. The stored assignment remains business_details.account_manager_id. User detail includes these fields in both basic and full responses.

Optional is_account_manager=true|false and column_filters={"is_account_manager":true} filter tenant memberships before pagination. The designation filter also applies to total, statusCounts and stats. Both supplied filters apply together. This informational designation does not change user roles.

Query parameters

ParameterTypeDescription
languageOptionalstring

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

Example: en

pageOptionalstring

Page number for pagination (Optional, default: 1)

Example: 1

limitOptionalstring

Number of records per page (Optional, default: 10)

Example: 10

statusOptionalstring

Filter users by status (Optional, values: active, inactive, pending, blocked)

Example: active

searchOptionalstring

Search term to filter users by email, first name, or last name (Optional)

tenant_idOptionalstring

Filter users by tenant ID (Optional)

role_idOptionalstring

Filter users by role ID (Optional) exact role-ID filter

startDateOptionalstring

Filter users created after this date (ISO format, optional)

endDateOptionalstring

Filter users created before this date (ISO format, optional)

roleOptionalstring

Filter users by exact role name. Supports comma-separated values for multiple roles, e.g. driver,manager (Optional) exact role-name filter. Supports comma-separated values such as driver,manager

Example: driver,manager

is_businessOptionalstring

Filter by business type: 'true' for business users, 'false' for individual users (Optional)

show_allOptionalstring

Show all users regardless of filters (Optional, default: true)

Example: true

include_customerOptionalstring

Include users without any assigned roles (customers) in the results (Optional, default: true). Set to 'false' to exclude users without roles.

Example: true

exclude_customersstring

Alias for excluding customer rows. Set to 'true' to force include_customer=false.

is_market_userOptionalstring

Filter users by market-context flag (true or false) (Optional)

market_idOptionalstring

Filter users by market ID (Optional)

is_restaurant_userOptionalstring

Filter users by restaurant-context flag (true or false) (Optional)

restaurant_location_idOptionalstring

Filter users by restaurant location ID (Optional)

sort_bystring

Sort by one of: full_name, email, phone, status, role_name, user_type, created_at, last_login_at

sort_orderstring

Sort order: asc or desc. Defaults to desc when sort_by is provided. asc or desc (defaults to desc when sort_by is provided)

column_filtersstring

JSON string with column filters. Supported keys: full_name, email, phone, status, role, role_id, role_name, role_scope, user_type, is_market_user, is_restaurant_user, created_at, last_login_at Also accepts is_account_manager as boolean true/false or strings "true"/"false". JSON string with keys full_name, email, phone, status, role, role_id, role_name, role_scope, user_type, is_market_user, is_restaurant_user, created_at, last_login_at

Example: {"role_id":"role-uuid"}

is_account_managerOptionalstring

Optional true/false designation filter. Also supported in column_filters; applies to rows, totals, statusCounts and stats.

column_filters.role_idstring

exact role-ID filter, for example {"role_id":"e103e9a4-8c78-4c95-a27a-893d278ac590"}

column_filters.rolestring

exact role-name filter with comma-separated values such as driver,manager

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/users?language=en&page=1&limit=10&status=active&search=&tenant_id=&role_id=&role=driver,manager&is_business=&startDate=&endDate=&show_all=true&include_customer=true&exclude_customers=&is_market_user=&market_id=&is_restaurant_user=&restaurant_location_id=&sort_by=&sort_order=&column_filters=&is_account_manager=" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200Success Response
Response body
json
1{2  "success": true,3  "message": "Users retrieved successfully",4  "users": [5    {6      "id": "user-uuid",7      "email": "[email protected]",8      "first_name": "John",9      "last_name": "Doe",10      "full_name": "John Doe",11      "phone": "+1234567890",12      "avatar_url": "https://example.com/avatar.jpg",13      "status": "active",14      "is_market_user": true,15      "market_id": "market-uuid",16      "is_restaurant_user": false,17      "restaurant_location_id": null,18      "user_type": "business",19      "roles": {20        "id": "role-uuid",21        "name": "admin",22        "scope": "tenant"23      },24      "tenant": {25        "id": "tenant-uuid",26        "name": "Example Tenant"27      },28      "created_at": "2023-01-01T00:00:00Z",29      "last_login_at": "2023-01-15T10:30:00Z",30      "is_account_manager": false,31      "account_manager": null32    }33  ],34  "stats": {35    "totalUsers": 42,36    "activeUsers": 30,37    "pendingUsers": 8,38    "recentlyJoined": [39      {40        "id": "user-uuid",41        "full_name": "John Doe",42        "avatar_url": "https://example.com/avatar.jpg",43        "created_at": "2025-09-20T10:42:09.258391+00:00"44      }45    ]46  },47  "page": 1,48  "limit": 10,49  "total": 42,50  "statusCounts": {51    "active": 30,52    "inactive": 8,53    "pending": 4,54    "total": 4255  }56}

Get User by ID​

Platform & Access / Users
GET/users/:id

Retrieves a specific user by ID after target-scope authorization.

Authentication and access

  • Self-detail remains available.

  • manage:user_access:system may read any target.

  • manage:user_access:tenant may read only a target with an active membership in the caller's exact tenant and no system-scoped role.

  • Missing caller permission returns 403 rbac_forbidden before target lookup.

  • Missing, inactive, foreign-tenant, or system-scoped targets return scope-safe 404 user_not_found without contact or role data.

Both success modes include user.can_reset_password. It is true only when the current JWT caller may call PUT /users/:id/reset-password for that returned user. Full-mode stats and enrichment run only after authorization succeeds.

Frontend integration

open the detail page only on 200. Treat 403 as denied caller access and 404 as unavailable/out-of-scope; do not render cached target data. Show Reset Password only when user.can_reset_password is true.

Account managers

each returned user includes is_account_manager (tenant designation, independent of role permissions) and account_manager: null or { "id": "user UUID", "name": "manager full name", "is_account_manager": true }. Existing assignments remain visible after their manager is unmarked, with account_manager.is_account_manager: false. Names come from one batched lookup of current user records. The stored assignment remains business_details.account_manager_id. User detail includes these fields in both basic and full responses.

Path parameters

ParameterTypeDescription
idRequiredstring

UUID of the user to retrieve (Required)

Example: user-uuid

Query parameters

ParameterTypeDescription
languageOptionalstring

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

Example: en

response_typestring

Response detail level: 'full' (default) returns all data including roles, permissions, teams, stats; 'basic' returns only essential profile info (id, name, email, phone, status, avatar, user_type, last_login, created_at) =full (default): roles, permissions, teams, tenant, driver/warehouse context, and stats. =basic: essential profile fields.

Example: full

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/users/${ID}?language=en&response_type=full" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200Success - Full (default)
Response body
json
1{2  "success": true,3  "message": "User retrieved successfully",4  "user": {5    "id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",6    "avatar_url": "https://example.com/avatars/a1b2c3d4.png",7    "email": "[email protected]",8    "first_name": "John",9    "last_name": "Doe",10    "full_name": "John Doe",11    "phone": "+15551234567",12    "status": "active",13    "user_type": "member",14    "role": {15      "id": "role-uuid-789",16      "name": "tenant_member",17      "scope": "tenant"18    },19    "tenant_info": {20      "id": "tenant-uuid-abc",21      "name": "Example Tenant Inc.",22      "slug": "example-tenant-inc",23      "status": "active",24      "metadata": {25        "industry": "tech"26      },27      "contact_info": {28        "address": "123 Main St"29      }30    },31    "panel_type": "customer",32    "permissions": [33      "view_deliveries",34      "create_deliveries"35    ],36    "teams": [37      {38        "id": "team-uuid",39        "name": "Team A"40      }41    ],42    "all_teams": false,43    "created_at": "2023-01-15T10:30:00Z",44    "updated_at": "2023-10-20T14:45:00Z",45    "last_login_at": "2023-11-01T09:00:00Z",46    "requires_driver_setup": false,47    "can_reset_password": true,48    "is_account_manager": false,49    "account_manager": null50  },51  "stats": {52    "wallet_balance": 150.5,53    "total_orders": 42,54    "pending_payouts": 2,55    "active_api_keys": 156  }57}

Get User Accounting​

Platform & Access / Users
GET/users/:userId/accounting

Returns the accounting summary used by the user-detail Accounting tab. This endpoint is driver-linked: the user must have a driver record in the tenant.

Includes

  • Balance summary for selected period (creditor/debtor)

  • Cash collected (COD + order fee)

  • Earnings (cash vs online/credit)

  • Order costs (cash/paid-by-receiver/card/wallet)

  • Company earnings

  • Paid withdrawals

  • From support buckets

  • Self OR tenant permissions (finance/operations/tenant view).

Path parameters

ParameterTypeDescription
userIdRequiredstring

User UUID (required)

Example: {{user_id}}

Query parameters

ParameterTypeDescription
tenant_idOptionalstring

Tenant ID (optional, defaults to auth context)

from_dateOptionalstring

From date (ISO8601, optional)

to_dateOptionalstring

To date (ISO8601, optional)

languageOptionalstring

Language code (optional)

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}/accounting?tenant_id=&from_date=&to_date=&language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200Success
Response body
json
1{2  "success": true,3  "message": "user_accounting_retrieved_successfully",4  "data": {5    "period": {6      "from_date": null,7      "to_date": null8    },9    "currency": "IQD",10    "balance_summary": {11      "amount": 0,12      "type": "creditor",13      "description": "Account is settled."14    },15    "cash_collected": {16      "total": 0,17      "breakdown": {18        "cod": 0,19        "order_fee": 020      }21    },22    "earnings": {23      "total": 0,24      "breakdown": {25        "cash": 0,26        "online_credit": 027      }28    },29    "order_costs": {30      "total": 0,31      "breakdown": {32        "cash": 0,33        "paid_by_receiver": 0,34        "card": 0,35        "wallet": 036      }37    },38    "company_earnings": 0,39    "paid_withdrawals": 0,40    "from_support": {41      "cod_type_creditor": 0,42      "cod_type_debtor": 0,43      "support_type_creditor": 0,44      "support_type_debtor": 045    }46  }47}

Invite User​

Platform & Access / Users
POST/users

authenticated JWT or API key. API keys inherit their owner’s permissions; the key’s system/tenant scope remains a ceiling. The api_keys.permissions=[] field is not reinterpreted. The deprecated is_system_admin flag and role names do not authorize these operations.

Errors use {success:false, code, message}; message is localized (en/ar/fa/ku). 403: rbac_forbidden or rbac_invalid_scope. 409: rbac_preservation_conflict, role_already_exists, or the existing last-administrator protection where applicable. Unexpected database errors return 500 with a safe localized message.

Requires manage

user_access:system, or manage:user_access:tenant for the caller’s current tenant. Broad manage:system, manage:tenant and manage:users:tenant permissions do not substitute. Tenant callers may grant only tenant roles whose complete permission set is a subset of their effective permissions. They cannot change system users or operate across tenants. Role scope must match role_type. System assignments have tenant_id=null. Tenant assignments require tenant_id.

Invite a user by email. Required: email, first_name, last_name. Optional: avatar_url, role_id, role_type, tenant_id, user_type, business_details. For add/invite, omitted role_id, null, and "" all mean no role assignment. Non-empty values must be valid role UUIDs and still pass role scope and permission checks. Invalid role values return HTTP 400 with a localized message naming role_id and field-level details; frontend forms can display that message directly. Caller authorization and selected-role validation occur before the invitation email is sent.

role_type defaults to system for manage:user_access:system callers, otherwise tenant. Tenant callers default tenant_id to their own tenant. System callers assigning a tenant role must send role_type=tenant and a valid tenant_id. Authorization and selected-role validation precede Auth work. Existing accounts return 409. Provisioning failures return 500 rbac_provisioning_incomplete; records are retained and disabled, never deleted. Success remains 201 {success:true,message,data}, with data containing the Auth user.

Frontend integration

gate controls using the documented permission names and scope. Display localized message for failures; branch on code/status, never message. Handle 403 as denied access and 409 as a conflict requiring correction. For add/invite only, handle 500 rbac_provisioning_incomplete as an incomplete account kept disabled for administrator recovery. No navigation action is added.

Invitation completion

a pending invited account becomes active when its password changes after its email was confirmed. Supabase assigns a temporary password during invite acceptance, so activation must not depend on the previous password being empty. Accepting the link alone does not activate the account. Inactive accounts remain inactive; ordinary sign-ins and manually created pending accounts retain their status. A confirmed pending invitee can finish activation by setting or resetting their password. Frontend: preserve the Auth session on /update-password so pending invitees can finish password setup.

Contact phone validation

business_details.contact_person.phone remains optional (omitted, null or blank is accepted). A supplied value must contain at least two main-number digits and use phone formatting. Local and international formats, Unicode decimal digits, spaces, parentheses, dots, slashes, hyphens and one leading + are accepted. Optional extensions use ext, ext., extension, x, # or ;ext=. Extensions do not count toward the main-number length. No country-specific length or country code is enforced, and the submitted value is preserved. This checks syntax, not whether the number exists or is reachable. Invalid values return HTTP 400 with a plain localized contact-phone message, without an internal field-path prefix. The field identifier remains in structured details for clients; display the top-level message in the portal toast. Validation runs before invitation email delivery or account writes.

Invitation delivery errors

Auth failures keep HTTP 400 and return {success:false,code,message}. Codes are invitation_email_rejected, invitation_email_not_authorized, invitation_rate_limited, user_already_exists, or invitation_failed. Recipient errors also include details:[{field:"email",message}]. Messages explain the failed step and the next action; raw provider errors are never returned. Unknown Auth failures cannot reliably identify the SMTP cause, so the fallback asks the user to check the address and supported test address, then asks an administrator to check delivery settings. Existing application-account lookup conflicts remain HTTP 409. For staging invitation tests use a controlled real mailbox or the email provider's supported test recipient, such as [email protected]; example.com recipients are rejected by the current provider. Frontend forms can continue showing the localized message; no response navigation changes.

Account-manager assignment

business_details.account_manager_id accepts a designated same-tenant user UUID, null, or an empty string to clear. A new/changed manager must have is_account_manager=true; creation validates before creating the Auth account. Update preserves an unchanged assignment even after the manager is unmarked. Invalid manager: HTTP 400, code: invalid_account_manager_id, localized message. Assignment changes require manage:user_access:tenant for the target tenant or manage:user_access:system.

Optional is_account_manager must be a boolean (null is rejected). It is a tenant designation, grants no permissions and defaults to false for new memberships. Unmarking keeps all existing assignments but excludes the user from future picker results. Update omission preserves the flag; ordinary self-profile access cannot change it. A supplied tenant ID must match the target membership. Forbidden permissions/scope: HTTP 403. Frontend: refresh the existing user/customer data and picker after a successful change.

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
emailstring—
first_namestring—
last_namestring—
avatar_urlstring—
role_idstring—
is_account_managerboolean—
business_detailsobject—
business_details.account_manager_idnull—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/users?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "email": "[email protected]",6  "first_name": "John",7  "last_name": "Doe",8  "avatar_url": "https://example.com/avatar.png",9  "role_id": "role-uuid-optional",10  "is_account_manager": false,11  "business_details": {12    "account_manager_id": null13  }14}'

Response examples

201Created
Response body
json
1{2  "success": true,3  "message": "invitation_sent_successfully",4  "data": {5    "user": {6      "id": "new-user-uuid",7      "email": "[email protected]"8    }9  }10}

Add User (auto-confirm)​

Platform & Access / Users
POST/users/add

authenticated JWT or API key. API keys inherit their owner’s permissions; the key’s system/tenant scope remains a ceiling. The api_keys.permissions=[] field is not reinterpreted. The deprecated is_system_admin flag and role names do not authorize these operations.

Errors use {success:false, code, message}; message is localized (en/ar/fa/ku). 403: rbac_forbidden or rbac_invalid_scope. 409: rbac_preservation_conflict, role_already_exists, or the existing last-administrator protection where applicable. Unexpected database errors return 500 with a safe localized message.

Requires manage

user_access:system, or manage:user_access:tenant for the caller’s current tenant. Broad manage:system, manage:tenant and manage:users:tenant permissions do not substitute. Tenant callers may grant only tenant roles whose complete permission set is a subset of their effective permissions. They cannot change system users or operate across tenants. Role scope must match role_type. System assignments have tenant_id=null. Tenant assignments require tenant_id.

Create an auto-confirmed user. Required: password and at least one of email/phone. Optional: first_name, last_name, avatar_url, role_id, role_type, tenant_id, is_staff, user_type, business_details. For add/invite, omitted role_id, null, and "" all mean no role assignment. Non-empty values must be valid role UUIDs and still pass role scope and permission checks. Invalid role values return HTTP 400 with a localized message naming role_id and field-level details; frontend forms can display that message directly. Only manage:user_access:system permits is_staff=true. Auth creation starts banned; successful membership/role provisioning is followed by unbanning.

role_type defaults to system for manage:user_access:system callers, otherwise tenant. Tenant callers default tenant_id to their own tenant. System callers assigning a tenant role must send role_type=tenant and a valid tenant_id. Authorization and selected-role validation precede Auth work. Existing accounts return 409. Provisioning failures return 500 rbac_provisioning_incomplete; records are retained and disabled, never deleted. Success remains 201 {success:true,message,data}, with data containing the Auth user.

Frontend integration

gate controls using the documented permission names and scope. Display localized message for failures; branch on code/status, never message. Handle 403 as denied access and 409 as a conflict requiring correction. For add/invite only, handle 500 rbac_provisioning_incomplete as an incomplete account kept disabled for administrator recovery. No navigation action is added.

Account status

optional status accepts active, inactive, or pending; defaults to pending when omitted (or null). After successful role/membership provisioning, the selected application status is persisted before authentication is unbanned. Email/phone remain auto-confirmed. Pending/inactive accounts cannot access application APIs until an administrator activates them. Frontend: Add User status selector defaults to Active and explicitly sends the selection. Existing API clients that omit status now create pending accounts.

Contact phone validation

business_details.contact_person.phone remains optional (omitted, null or blank is accepted). A supplied value must contain at least two main-number digits and use phone formatting. Local and international formats, Unicode decimal digits, spaces, parentheses, dots, slashes, hyphens and one leading + are accepted. Optional extensions use ext, ext., extension, x, # or ;ext=. Extensions do not count toward the main-number length. No country-specific length or country code is enforced, and the submitted value is preserved. This checks syntax, not whether the number exists or is reachable. Invalid values return HTTP 400 with a plain localized contact-phone message, without an internal field-path prefix. The field identifier remains in structured details for clients; display the top-level message in the portal toast. Validation runs before invitation email delivery or account writes.

Account-manager assignment

business_details.account_manager_id accepts a designated same-tenant user UUID, null, or an empty string to clear. A new/changed manager must have is_account_manager=true; creation validates before creating the Auth account. Update preserves an unchanged assignment even after the manager is unmarked. Invalid manager: HTTP 400, code: invalid_account_manager_id, localized message. Assignment changes require manage:user_access:tenant for the target tenant or manage:user_access:system.

Optional is_account_manager must be a boolean (null is rejected). It is a tenant designation, grants no permissions and defaults to false for new memberships. Unmarking keeps all existing assignments but excludes the user from future picker results. Update omission preserves the flag; ordinary self-profile access cannot change it. A supplied tenant ID must match the target membership. Forbidden permissions/scope: HTTP 403. Frontend: refresh the existing user/customer data and picker after a successful change.

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
emailstring—
phonestring—
passwordstring—
first_namestring—
last_namestring—
avatar_urlstring—
is_staffboolean—
role_idstring—
statusstring—
is_account_managerboolean—
business_detailsobject—
business_details.account_manager_idnull—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/users/add?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "email": "[email protected]",6  "phone": "+1234567890",7  "password": "SecurePassword123!",8  "first_name": "New",9  "last_name": "User",10  "avatar_url": "https://example.com/avatar.png",11  "is_staff": false,12  "role_id": "role-uuid-optional",13  "status": "active",14  "is_account_manager": false,15  "business_details": {16    "account_manager_id": null17  }18}'

Response examples

201Created
Response body
json
1{2  "success": true,3  "message": "user_created_successfully",4  "data": {5    "user": {6      "id": "new-user-uuid",7      "email": "[email protected]",8      "phone": "+1234567890"9    }10  }11}

Add Customer (auto-confirm phone)​

Platform & Access / Users
POST/users/add-customer

Creates a customer user with phone and name. Phone is auto-confirmed; email is optional. Password is generated automatically.

Contact phone validation

business_details.contact_person.phone remains optional (omitted, null or blank is accepted). A supplied value must contain at least two main-number digits and use phone formatting. Local and international formats, Unicode decimal digits, spaces, parentheses, dots, slashes, hyphens and one leading + are accepted. Optional extensions use ext, ext., extension, x, # or ;ext=. Extensions do not count toward the main-number length. No country-specific length or country code is enforced, and the submitted value is preserved. This checks syntax, not whether the number exists or is reachable. Invalid values return HTTP 400 with a plain localized contact-phone message, without an internal field-path prefix. The field identifier remains in structured details for clients; display the top-level message in the portal toast. Validation runs before invitation email delivery or account writes.

Account-manager assignment

business_details.account_manager_id accepts a designated same-tenant user UUID, null, or an empty string to clear. A new/changed manager must have is_account_manager=true; creation validates before creating the Auth account. Update preserves an unchanged assignment even after the manager is unmarked. Invalid manager: HTTP 400, code: invalid_account_manager_id, localized message. Assignment changes require manage:user_access:tenant for the target tenant or manage:user_access:system.

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
namestring—
phonestring—
emailstring—
business_detailsobject—
business_details.account_manager_idnull—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/users/add-customer?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "name": "Jane Customer",6  "phone": "+1234567890",7  "email": "[email protected]",8  "business_details": {9    "account_manager_id": null10  }11}'

Response examples

201Created
Response body
json
1{2  "success": true,3  "message": "user_created_successfully",4  "data": {5    "user": {6      "id": "customer-uuid",7      "phone": "+1234567890",8      "email": "[email protected]"9    }10  }11}

Resend User Invitation​

Platform & Access / Users
POST/users/resend-invitation

Resends an invitation email to a user who was previously invited but may not have received or acted on the original invitation.

  • Must have appropriate permissions to invite users

Endpoint

POST /users/resend-invitation

Required Fields

  • email: Valid email address of the user to resend invitation to

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
emailstring—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/users/resend-invitation?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "email": "[email protected]"6}'

Response examples

200Success Response
Response body
json
1{2  "success": true,3  "message": "Invitation resent successfully",4  "data": {5    "user": {6      "id": "user-uuid",7      "email": "[email protected]",8      "invited_at": "2023-01-01T00:00:00Z"9    }10  }11}

List Customers​

Platform & Access / Users
GET/users/customers

Lists customers (users without assigned roles) with pagination, search, sorting, column filters, and KPI stats. Legacy top-level is_business still works.

  • Requires tenant context

Response

Account managers

each returned user includes is_account_manager (tenant designation, independent of role permissions) and account_manager: null or { "id": "user UUID", "name": "manager full name", "is_account_manager": true }. Existing assignments remain visible after their manager is unmarked, with account_manager.is_account_manager: false. Names come from one batched lookup of current user records. The stored assignment remains business_details.account_manager_id. User detail includes these fields in both basic and full responses.

Query parameters

ParameterTypeDescription
languageOptionalstring

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

Example: en

pageOptionalstring

Page number for pagination (Optional, default: 1)

Example: 1

limitOptionalstring

Number of records per page (Optional, default: 10)

Example: 10

tenant_idOptionalstring

Tenant ID to filter customers (Optional, uses context if not provided)

Example: {{tenant_id}}

is_businessOptionalstring

Filter by customer type: true for business, false for individual (Optional)

is_market_userOptionalstring

Filter customers by market-context flag (true or false) (Optional)

market_idOptionalstring

Filter customers by market ID (Optional)

is_restaurant_userOptionalstring

Filter customers by restaurant-context flag (true or false) (Optional)

restaurant_location_idOptionalstring

Filter customers by restaurant location ID (Optional)

searchstring

Toolbar search across first_name, last_name, full_name, email, and phone

sort_bystring

Sort by one of: name, email, created_at, last_login_at

sort_orderstring

Sort order: asc or desc. Defaults to desc when sort_by is provided.

column_filtersstring

JSON string with keys: full_name, email, phone, status, user_type, is_market_user, is_restaurant_user, created_at, last_login_at JSON string with keys full_name, email, phone, status, user_type, is_market_user, is_restaurant_user, created_at, last_login_at

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

More examples
Response
json
1{2  "success": true,3  "message": "Users retrieved successfully",4  "users": [5    {6      "id": "user-uuid",7      "email": "[email protected]",8      "first_name": "Jane",9      "last_name": "Doe",10      "full_name": "Jane Doe",11      "user_type": "individual",12      "is_market_user": false,13      "market_id": null,14      "is_restaurant_user": false,15      "restaurant_location_id": null,16      "roles": {17        "id": null,18        "name": "customer",19        "scope": "tenant"20      },21      "tenant": {22        "id": "tenant-uuid",23        "name": "Example Tenant"24      },25      "last_login_at": "2025-09-22T08:30:00Z"26    }27  ],28  "stats": {29    "totalUsers": 25,30    "activeUsers": 20,31    "pendingUsers": 3,32    "recentlyJoined": [33      {34        "id": "user-uuid",35        "first_name": "Jane",36        "last_name": "Doe",37        "email": "[email protected]",38        "avatar_url": null,39        "created_at": "2025-09-20T10:42:09.258391+00:00"40      }41    ]42  },43  "page": 1,44  "limit": 10,45  "total": 2546}
Staging
Request
curl
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/users/customers?language=en&page=1&limit=10&tenant_id=${TENANT_ID}&is_business=&is_market_user=&market_id=&is_restaurant_user=&restaurant_location_id=&search=&sort_by=&sort_order=&column_filters=" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200Success Response
Response body
json
1{2  "success": true,3  "message": "Users retrieved successfully",4  "users": [5    {6      "id": "user-uuid",7      "email": "[email protected]",8      "first_name": "Jane",9      "last_name": "Doe",10      "full_name": "Jane Doe",11      "user_type": "individual",12      "is_market_user": false,13      "market_id": null,14      "is_restaurant_user": false,15      "restaurant_location_id": null,16      "roles": {17        "id": null,18        "name": "customer",19        "scope": "tenant"20      },21      "tenant": {22        "id": "tenant-uuid",23        "name": "Example Tenant"24      },25      "is_account_manager": false,26      "account_manager": null27    }28  ],29  "stats": {30    "totalUsers": 25,31    "activeUsers": 20,32    "pendingUsers": 3,33    "recentlyJoined": [34      {35        "id": "user-uuid",36        "first_name": "Jane",37        "last_name": "Doe",38        "email": "[email protected]",39        "avatar_url": null,40        "created_at": "2025-09-20T10:42:09.258391+00:00"41      }42    ]43  },44  "page": 1,45  "limit": 10,46  "total": 2547}

Update User​

Platform & Access / Users
PUT/users/:id

Updates a user's information. Users can update their own information, while system administrators can update any user.

  • Users can update their own profile (including status)

  • System administrators / tenant administrators can update any user profile (email change is self-only)

Updatable Fields

  • email: Change login email (self-only). Triggers a confirmation email and redirects to sign-in after confirmation.

  • first_name: User's first name

  • last_name: User's last name

  • status: User account status (self or admin)

  • phone: User's phone number

  • avatar_url: URL to user's profile image

  • profile_data: Object containing additional profile information

  • customer_delivery_preferences: Object containing delivery preferences

Notes

  • Only provided fields will be updated. Fields not included in the request will remain unchanged.

  • Email change sends a confirmation email via Supabase Auth and is allowed only when updating your own user.

  • When email is updated, the API returns immediately with requires_email_confirmation: true and new_email. The frontend should display the message instructing the user to check their new email inbox to confirm.

Response

Status editing

pending users may now be edited, activated, or deactivated through this endpoint. A supplied status must be active or inactive; omit it to preserve the existing status, including pending. Existing authorization and administrator deactivation protections remain in effect. Status affects the global application account, not an individual tenant membership, and does not confirm email or set a password. Frontend: show the current status, including Pending as a display-only option, submit status only when changed, keep the modal open on failure, and refresh the user on success. An unsupported status returns HTTP 400.

Contact phone validation

business_details.contact_person.phone remains optional (omitted, null or blank is accepted). A supplied value must contain at least two main-number digits and use phone formatting. Local and international formats, Unicode decimal digits, spaces, parentheses, dots, slashes, hyphens and one leading + are accepted. Optional extensions use ext, ext., extension, x, # or ;ext=. Extensions do not count toward the main-number length. No country-specific length or country code is enforced, and the submitted value is preserved. This checks syntax, not whether the number exists or is reachable. Invalid values return HTTP 400 with a plain localized contact-phone message, without an internal field-path prefix. The field identifier remains in structured details for clients; display the top-level message in the portal toast. Validation runs before invitation email delivery or account writes.

Account-manager assignment

business_details.account_manager_id accepts a designated same-tenant user UUID, null, or an empty string to clear. A new/changed manager must have is_account_manager=true; creation validates before creating the Auth account. Update preserves an unchanged assignment even after the manager is unmarked. Invalid manager: HTTP 400, code: invalid_account_manager_id, localized message. Assignment changes require manage:user_access:tenant for the target tenant or manage:user_access:system.

Optional is_account_manager must be a boolean (null is rejected). It is a tenant designation, grants no permissions and defaults to false for new memberships. Unmarking keeps all existing assignments but excludes the user from future picker results. Update omission preserves the flag; ordinary self-profile access cannot change it. A supplied tenant ID must match the target membership. Forbidden permissions/scope: HTTP 403. Frontend: refresh the existing user/customer data and picker after a successful change.

Path parameters

ParameterTypeDescription
idRequiredstring

UUID of the user to update (Required)

Example: user-uuid

Query parameters

ParameterTypeDescription
languageOptionalstring

Language code for response localization (Optional, default: en) Language code for localized response messages (e.g., 'en')

Example: en

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
emailstring—
first_namestring—
last_namestring—
statusstring—
phonestring—
avatar_urlstring—
profile_dataobject—
profile_data.biostring—
profile_data.preferencesobject—
profile_data.preferences.themestring—
profile_data.preferences.notificationsboolean—
customer_delivery_preferencesobject—
Show 5 more body fields

Additional body parameters

ParameterTypeDescription
customer_delivery_preferences.preferred_timestring—
customer_delivery_preferences.special_instructionsstring—
is_account_managerboolean—
business_detailsobject—
business_details.account_manager_idnull—
Staging
Request
curl
1curl --globoff --request PUT "https://staging-api.ondi.io/functions/v1/users/${ID}?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "email": "[email protected]",6  "first_name": "Updated",7  "last_name": "Name",8  "status": "inactive",9  "phone": "+9876543210",10  "avatar_url": "https://example.com/avatar.jpg",11  "profile_data": {12    "bio": "Updated user bio",13    "preferences": {14      "theme": "dark",15      "notifications": true16    }17  },18  "customer_delivery_preferences": {19    "preferred_time": "evening",20    "special_instructions": "Leave at the door"21  },22  "is_account_manager": false,23  "business_details": {24    "account_manager_id": null25  }26}'
Other request examples (1)
Request example 1
json
1{2  "email": "[email protected]",3  "first_name": "Updated",4  "last_name": "Name",5  "status": "inactive",6  "phone": "+9876543210",7  "avatar_url": "https://example.com/avatar.jpg",8  "profile_data": {9    "bio": "Updated user bio",10    "preferences": {11      "theme": "dark",12      "notifications": true13    }14  },15  "customer_delivery_preferences": {16    "preferred_time": "evening",17    "special_instructions": "Leave at the door"18  }19}

Response examples

200Success Response - Regular Update
Response body
json
1{2  "success": true,3  "message": "User updated successfully",4  "user": {5    "id": "user-uuid",6    "email": "[email protected]",7    "first_name": "Updated",8    "last_name": "Name",9    "status": "inactive",10    "phone": "+9876543210",11    "avatar_url": "https://example.com/avatar.jpg",12    "profile_data": {13      "bio": "Updated user bio",14      "preferences": {15        "theme": "dark",16        "notifications": true17      }18    },19    "customer_delivery_preferences": {20      "preferred_time": "evening",21      "special_instructions": "Leave at the door"22    },23    "created_at": "2023-01-01T00:00:00Z",24    "updated_at": "2023-01-01T00:00:00Z"25  }26}

Update My Password​

Platform & Access / Users
PUT/users/me/password

Updates the current authenticated user's password.

  • Requires valid authenticated user bearer token

  • API key authentication is not allowed

Required Fields

  • new_password: New password (min: 8, max: 128 characters)

Optional Fields

  • current_password: Required when the user already has a password. Omit it only when setting a password for the first time after OTP or magic link sign-in.

Notes

  • The authenticated user is always the target user

  • Users with an existing password must provide the correct current password

  • Users without an existing password can set one by providing only new_password

Query parameters

ParameterTypeDescription
languageOptionalstring

Language code for response localization (Optional, default: en) Language code for localized response messages (e.g., 'en')

Example: en

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
current_passwordstring—
new_passwordstring—
Staging
Request
curl
1curl --globoff --request PUT "https://staging-api.ondi.io/functions/v1/users/me/password?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "current_password": "CurrentSecurePassword123!",6  "new_password": "NewSecurePassword123!"7}'

Response examples

200Success Response
Response body
json
1{2  "success": true,3  "message": "Password updated successfully",4  "data": {5    "user_id": "user-uuid"6  }7}

Update My Notification Settings​

Platform & Access / Users
PUT/users/me/notifications

Enable or disable in-app/push/email/SMS notifications for the current user.

When notifications_enabled is false, new rows in notifications are stored with status skipped_disabled_notification and are not delivered; they do not appear in GET /messaging/notifications (which only returns status: sent).

Bearer token only (API key not supported).

Response (200)

{ success, message, data: { user_id, notifications_enabled } }

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
notifications_enabledRequiredboolean—
Staging
Request
curl
1curl --globoff --request PUT "https://staging-api.ondi.io/functions/v1/users/me/notifications?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "notifications_enabled": true6}'

Response examples

200Success Response
Response body
json
1{2  "success": true,3  "message": "Notification settings updated successfully",4  "data": {5    "user_id": "user-uuid",6    "notifications_enabled": true7  }8}

Reset User Password by ID​

Platform & Access / Users
PUT/users/:id/reset-password

Resets a user's password by ID after permission and target-scope authorization.

  • JWT only; API keys return 403 rbac_forbidden.

  • manage:user_access:system may reset any target.

  • manage:user_access:tenant may reset only a target with an active membership in the caller's exact tenant and no system-scoped role.

  • Role names and the deprecated is_system_admin flag do not authorize this operation.

The complete target boundary is checked in one indexed query before auth.admin.updateUserById. Missing caller permission returns 403 rbac_forbidden. Missing, inactive, foreign-tenant, or system-scoped targets return scope-safe 404 user_not_found and the password is unchanged.

{"password":"NewSecurePassword123!"}; password length is 8-128 characters. Success remains 200 with data.user_id, email, and phone.

Frontend integration

send the returned user ID only when user.can_reset_password from GET /users/:id is true. Handle 403 as denied caller access and 404 as unavailable/out-of-scope. The backend check remains authoritative.

Path parameters

ParameterTypeDescription
idRequiredstring

UUID of the user whose password to reset (Required)

Example: user-uuid

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
passwordstring—
Staging
Request
curl
1curl --globoff --request PUT "https://staging-api.ondi.io/functions/v1/users/${ID}/reset-password?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "password": "NewSecurePassword123!"6}'

Response examples

200Success Response
Response body
json
1{2  "success": true,3  "message": "User updated successfully",4  "data": {5    "user_id": "user-uuid",6    "email": "[email protected]",7    "phone": "+1234567890"8  }9}

Delete User​

Platform & Access / Users
DELETE/users/:id

Delete a user. This operation may be permanent or may mark the user as deleted depending on implementation.

Path parameters

ParameterTypeDescription
idRequiredstring

UUID of the user to delete (Required)

Example: user-uuid

Query parameters

ParameterTypeDescription
languageOptionalstring

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

Example: en

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request DELETE "https://staging-api.ondi.io/functions/v1/users/${ID}?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

204Success Response

Completed successfully. No response body.

Assign User Role​

Platform & Access / Users
POST/users/:userId/roles

authenticated JWT or API key. API keys inherit their owner’s permissions; the key’s system/tenant scope remains a ceiling. The api_keys.permissions=[] field is not reinterpreted. The deprecated is_system_admin flag and role names do not authorize these operations.

Errors use {success:false, code, message}; message is localized (en/ar/fa/ku). 403: rbac_forbidden or rbac_invalid_scope. 409: rbac_preservation_conflict, role_already_exists, or the existing last-administrator protection where applicable. Unexpected database errors return 500 with a safe localized message.

Requires manage

user_access:system, or manage:user_access:tenant for the caller’s current tenant. Broad manage:system, manage:tenant and manage:users:tenant permissions do not substitute. Tenant callers may grant only tenant roles whose complete permission set is a subset of their effective permissions. They cannot change system users or operate across tenants. Role scope must match role_type. System assignments have tenant_id=null. Tenant assignments require tenant_id.

Required

role_id. System user-access callers must explicitly send role_type. Tenant callers default role_type=tenant and tenant_id to their current tenant. Optional tenant_id for system callers assigning tenant roles. Malformed JSON, scalar bodies and malformed UUIDs return 400. Self changes return 403; changing the last active system_admin or tenant_admin returns 409. A business user without an existing role cannot be assigned one (400). Authorization uses the permissions already loaded by authentication middleware. Membership and role assignment use separate database writes; failures stop the remaining steps but earlier writes may persist. Existing assignments are updated in place. Audit logging uses the existing request middleware. Success remains 201 for a new assignment, 200 for an existing assignment, {success:true,message}.

Frontend integration

gate controls using the documented permission names and scope. Display localized message for failures; branch on code/status, never message. Handle 403 as denied access and 409 as a conflict requiring correction. For add/invite only, handle 500 rbac_provisioning_incomplete as an incomplete account kept disabled for administrator recovery. No navigation action is added.

Path parameters

ParameterTypeDescription
userIdRequiredstring

The unique identifier of the user to assign the role to

Query parameters

ParameterTypeDescription
languagestring

Language code for response messages (default: en)

Example: en

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
role_idstring—
role_typestring—
tenant_idstring—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/users/${USERID}/roles?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "role_id": "role-uuid",6  "role_type": "tenant",7  "tenant_id": "tenant-uuid"8}'

Response examples

201Created
Response body
json
1{2  "success": true,3  "message": "Role assigned successfully"4}

Get User Role​

Platform & Access / Users
GET/users/:userId/roles

Retrieves roles assigned to a user. This endpoint returns all roles associated with the specified user, including role name, scope, role type, and tenant ID.

Path parameters

ParameterTypeDescription
userIdRequiredstring

ID of the user to get roles for

Query parameters

ParameterTypeDescription
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}/roles?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Role retrieved successfully",4  "roles": [5    {6      "id": "role-123",7      "name": "Admin",8      "scope": "global",9      "role_type": "admin",10      "tenant_id": "tenant-456"11    },12    {13      "id": "role-789",14      "name": "Editor",15      "scope": "tenant",16      "role_type": "user",17      "tenant_id": "tenant-456"18    }19  ]20}

Remove User Role​

Platform & Access / Users
DELETE/users/:userId/roles/:roleId

authenticated JWT or API key. API keys inherit their owner’s permissions; the key’s system/tenant scope remains a ceiling. The api_keys.permissions=[] field is not reinterpreted. The deprecated is_system_admin flag and role names do not authorize these operations.

Errors use {success:false, code, message}; message is localized (en/ar/fa/ku). 403: rbac_forbidden or rbac_invalid_scope. 409: rbac_preservation_conflict, role_already_exists, or the existing last-administrator protection where applicable. Unexpected database errors return 500 with a safe localized message.

Requires manage

user_access:system, or manage:user_access:tenant for the caller’s current tenant. Broad manage:system, manage:tenant and manage:users:tenant permissions do not substitute. Tenant callers may grant only tenant roles whose complete permission set is a subset of their effective permissions. They cannot change system users or operate across tenants. Role scope must match role_type. System assignments have tenant_id=null. Tenant assignments require tenant_id.

Record removal is blocked by the preservation policy. Unauthorized callers receive 403. Authorized callers receive 409 rbac_preservation_conflict. No role, permission assignment, user assignment, or dependent record is deleted. There is no success response while this policy is active.

Frontend integration

gate controls using the documented permission names and scope. Display localized message for failures; branch on code/status, never message. Handle 403 as denied access and 409 as a conflict requiring correction. For add/invite only, handle 500 rbac_provisioning_incomplete as an incomplete account kept disabled for administrator recovery. No navigation action is added.

Path parameters

ParameterTypeDescription
userIdRequiredstring

ID of the user to remove the role from

roleIdRequiredstring

ID of the role to be removed from the user

Query parameters

ParameterTypeDescription
languageOptionalstring

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

Example: en

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request DELETE "https://staging-api.ondi.io/functions/v1/users/${USERID}/roles/${ROLEID}?language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

403rbac_forbidden
Response body
json
1{2  "success": false,3  "code": "rbac_forbidden",4  "message": "You do not have permission to perform this action."5}

Get User Addresses​

Platform & Access / Users
GET/users/:userId/addresses

Lists addresses belonging to a specific user with pagination. Users can only access their own addresses, while system administrators can access any user's addresses.

  • Users can only access their own addresses

  • System administrators can access any user's addresses

Path parameters

ParameterTypeDescription
userIdRequiredstring

UUID of the user whose addresses to retrieve (Required)

Example: user-uuid

Query parameters

ParameterTypeDescription
languageOptionalstring

Language code for response localization (Optional, default: en) Language code for localized response messages (e.g., 'en')

Example: en

pageOptionalstring

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

Example: 1

limitOptionalstring

Number of records per page (Optional, default: 10) Number of records per page (default: 10)

Example: 10

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/users/${USERID}/addresses?language=en&page=1&limit=10" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200Success Response
Response body
json
1{2  "success": true,3  "message": "Addresses retrieved successfully",4  "addresses": [5    {6      "id": "address-uuid",7      "user_id": "user-uuid",8      "title": "Home",9      "address_line1": "123 Main St",10      "address_line2": "Apt 4B",11      "city": "New York",12      "state": "NY",13      "postal_code": "10001",14      "country": "US",15      "is_primary": true,16      "is_active": true,17      "latitude": 40.7128,18      "longitude": -74.006,19      "created_at": "2023-01-01T00:00:00Z",20      "updated_at": "2023-01-01T00:00:00Z"21    }22  ],23  "total": 5,24  "page": 1,25  "limit": 1026}

List Account Managers​

Platform & Access / Users
GET/users/account-managers

Slim account-manager picker. Requires manage:user_access:tenant or manage:user_access:system and tenant access. Returns only users whose tenant_users.is_account_manager=true in the requested tenant, without a role or active-status condition. One focused joined database query filters and paginates; no statistics or detail requests. Stable order: user ID. Debounce search and load the next page while pagination.has_more is true. Display an existing unmarked assignment using account_manager from user/customer responses; never add it as a new picker option.

Query parameters

ParameterTypeDescription
tenant_idstring

Required when no tenant context; tenant callers may only request their own tenant.

Example: {{tenant_id}}

searchOptionalstring

Optional literal full-name substring; up to 100 characters.

pagestring

Positive integer.

Example: 1

limitstring

1–100; defaults to 25.

Example: 25

languagestring

Example: en

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/users/account-managers?tenant_id=${TENANT_ID}&search=&page=1&limit=25&language=en" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200Success
Response body
json
1{2  "success": true,3  "users": [4    {5      "id": "00000000-0000-4000-8000-000000000001",6      "full_name": "Alex Manager"7    }8  ],9  "pagination": {10    "page": 1,11    "limit": 25,12    "has_more": false13  }14}