Retrieve a paginated list of users, with support for advanced filtering and sorting.
| Permission | Description |
|---|---|
users.view_all | View all users with full information |
users.view_limited | View users with limited information (requires search parameter) |
Note: Users with users.view_limited permission but not users.view_all must provide at least one character in the search parameter, or provide user_id, otherwise the API will return 0 results.
| Name | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number for pagination |
search | string | No* | Search text. Users with users.view_all match first name, last name, username, email or phone; everyone else matches first name, last name and username only. Max 255 characters. Persian-normalized: see the note below. Required for limited access users |
email_status | string | No | Filter by email verification status: verified or not_verified. Requires users.view_all: silently ignored for limited access users |
phone_status | string | No | Filter by phone verification status: verified or not_verified. See the note below on what "verified" means. Requires users.view_all: silently ignored for limited access users |
user_id | string | No | Comma-separated user IDs to filter by, e.g. 12,48. At most 50; more is a 422. Entries that are not IDs match nothing |
trainer_license_status | string | No | Filter by trainer license state: pending (uploaded, awaiting review), approved, rejected, or none (no document uploaded). Requires users.view_all: silently ignored for limited access users |
sort_by | string | No | Sort by: name, email, phone, created_at, email_verified_at, phone_verified_at. Limited access users may sort by name and created_at only; any other value falls back to the default |
sort_order | string | No | Sort order: desc (default) or asc |
*Required when user has users.view_limited permission but not users.view_all
Default sort is by created_at (newest first).
Why limited viewers search less. A user without
users.view_allnever sees anyone else's email, phone or verification state, so nothing they can ask for may depend on them either. Matching a search against email or phone let such a user pin someone withuser_idand grow a guess one character at a time, learning the value from whether the account came back; sorting or filtering by those columns leaks the same way. For them,searchmatches name and username only, and the contact filters and sort keys are ignored.
What
phone_status=verifiedmeans.phone_verified_atis written when a one-time code has proven control of the number: at the end of registration and on a completed password reset. It is never accepted from a request on any endpoint, andPUT /api/v1/users/{user}clears it again whenever the phone number itself changes. An account created by an operator throughPOST /api/v1/usersis not verified: no code was ever sent.Accounts that registered before this was recorded still read as not verified until they next reset their password. An operator can close that gap in one pass with
php artisan users:backfill-phone-verification, which reports counts and writes nothing until it is given--write.
Accounts pending deletion are omitted. A user who has requested their own account deletion disappears from this listing the moment the request lands, not when the account is disposed of 14 days later. Holders of
users.view_allkeep seeing them, withdeletion_requested_atanddeletion_scheduled_aton the resource, so a support call about a pending deletion is answerable. The exclusion is derived from the viewer's permissions and is not a query parameter: a client cannot ask its way past it. SeePOST /api/v1/auth/delete-account.
searchmatching. The term and the text it is matched against are both normalized first, so an account whose name is stored with an Arabic keyboard'sي/كis found by a search typed with the Persianی/کand vice versa; the same applies to hamza-bearing alefs, teh marbuta, zero-width joiners and non-joiners, the tatweel, Arabic diacritics, and Persian or Arabic-Indic digits, so a phone number typed۰۹۱۲…finds one stored0912…. Matching is case-insensitive and matches anywhere in the text, not only at the start.%and_are literal characters:?search=%returns only accounts whose text actually contains a percent sign, not every account. A term that normalizes to nothing (whitespace, a lone zero-width non-joiner) is treated as no term at all, which for ausers.view_limitedviewer means the listing comes back empty.The five fields are matched as one string, so a term may span two of them in order:
"ali reza"matches the account whose first name isaliand last namereza. A term over 255 characters is rejected with 422.
{
"data": [
{<user resource>},
...
],
"links": {<pagination data>},
"meta": {<pagination data>}
}
| Status | Description | Reference |
|---|---|---|
| 401 | Unauthorized | Authentication error |
| 403 | Forbidden (no permission) | Permission error |
| 422 | search longer than 255 characters, or more than 50 IDs in user_id | Validation error |
| 429 | More than 60 requests a minute | Rate-limit error |