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": "user@example.com",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.

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 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"}

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=" \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": "user@example.com",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    }31  ],32  "stats": {33    "totalUsers": 42,34    "activeUsers": 30,35    "pendingUsers": 8,36    "recentlyJoined": [37      {38        "id": "user-uuid",39        "full_name": "John Doe",40        "avatar_url": "https://example.com/avatar.jpg",41        "created_at": "2025-09-20T10:42:09.258391+00:00"42      }43    ]44  },45  "page": 1,46  "limit": 10,47  "total": 42,48  "statusCounts": {49    "active": 30,50    "inactive": 8,51    "pending": 4,52    "total": 4253  }54}

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.

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": "user@example.com",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": true48  },49  "stats": {50    "wallet_balance": 150.5,51    "total_orders": 42,52    "pending_payouts": 2,53    "active_api_keys": 154  }55}

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 delivered+unique-label@resend.dev; example.com recipients are rejected by the current provider. Frontend forms can continue showing the localized message; no response navigation changes.

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—
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": "new.user@example.com",6  "first_name": "John",7  "last_name": "Doe",8  "avatar_url": "https://example.com/avatar.png",9  "role_id": "role-uuid-optional"10}'

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": "new.user@example.com"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.

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—
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": "new.user@example.com",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}'

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": "new.user@example.com",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.

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—
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": "optional@example.com"8}'

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": "optional@example.com"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": "user@example.com"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": "user@example.com",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

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": "customer@example.com",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": "customer@example.com",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": "customer@example.com",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    }26  ],27  "stats": {28    "totalUsers": 25,29    "activeUsers": 20,30    "pendingUsers": 3,31    "recentlyJoined": [32      {33        "id": "user-uuid",34        "first_name": "Jane",35        "last_name": "Doe",36        "email": "customer@example.com",37        "avatar_url": null,38        "created_at": "2025-09-20T10:42:09.258391+00:00"39      }40    ]41  },42  "page": 1,43  "limit": 10,44  "total": 2545}

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.

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 2 more body fields

Additional body parameters

ParameterTypeDescription
customer_delivery_preferences.preferred_timestring—
customer_delivery_preferences.special_instructionsstring—
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": "new.email@example.com",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}'

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": "user@example.com",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": "user@example.com",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}