Summary and recommendation
Strapi exposes two entirely separate REST user APIs that must not be conflated in an identity graph: the Users & Permissions plugin API at /api/users (end-users, JWT or API Token auth) and the admin panel API at /admin/users (admin panel accounts, admin JWT only).
Authentication flows, permission models, token lifetimes, and response shapes differ between them.
The /api/users endpoint returns a plain array rather than Strapi's standard { data, meta } paginated wrapper, which breaks generic Strapi client assumptions.
Pagination on /api/users uses either page-based (pagination[page] / pagination[pageSize]) or offset-based (pagination[start] / pagination[limit]) parameters, with a default page size of 25 and a maximum of 100.
API quick reference
| Has user API | Yes |
| Auth method | JWT Bearer token (Users & Permissions plugin) or API Token (admin-generated static token) |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | Enterprise |
Authentication
Auth method: JWT Bearer token (Users & Permissions plugin) or API Token (admin-generated static token)
Setup steps
- POST credentials to /api/auth/local to receive a JWT token.
- Include the JWT in subsequent requests as: Authorization: Bearer
. - Alternatively, generate an API Token in the Strapi Admin Panel under Settings > API Tokens and pass it as Authorization: Bearer
. - For admin-panel endpoints (/admin/*), authenticate via the admin JWT obtained from POST /admin/login.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| plugin::users-permissions.user.find | Read access to the users collection via the Users & Permissions plugin. | GET /api/users |
| plugin::users-permissions.user.findOne | Read a single user record. | GET /api/users/:id |
| plugin::users-permissions.user.create | Create a new user. | POST /api/users |
| plugin::users-permissions.user.update | Update an existing user. | PUT /api/users/:id |
| plugin::users-permissions.user.destroy | Delete a user. | DELETE /api/users/:id |
| plugin::users-permissions.user.me | Read the currently authenticated user's own profile. | GET /api/users/me |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | integer | Auto-incremented unique identifier. | auto | immutable | Primary key. |
| username | string | Unique username. | required | optional | Must be unique across users. |
| string | User email address. | required | optional | Must be unique; used for login. | |
| password | string | Hashed password. | required | optional | Never returned in API responses. |
| provider | string | Auth provider (e.g., local, google). | auto | read-only | Defaults to 'local'. |
| confirmed | boolean | Whether the user's email is confirmed. | auto (false) | optional | Set to true to bypass email confirmation. |
| blocked | boolean | Whether the user is blocked from logging in. | auto (false) | optional | Blocked users cannot authenticate. |
| role | relation (Role) | Assigned role object. | optional | optional | Defaults to 'Authenticated' role if not specified. |
| createdAt | datetime | Record creation timestamp. | auto | immutable | ISO 8601 format. |
| updatedAt | datetime | Last update timestamp. | auto | auto | ISO 8601 format. |
| resetPasswordToken | string | Token used for password reset flow. | null | system-managed | Not returned in standard API responses. |
| confirmationToken | string | Token used for email confirmation. | system-managed | system-managed | Not returned in standard API responses. |
Core endpoints
Register a new user (public)
- Method: POST
- URL:
/api/auth/local/register - Watch out for: Email confirmation may be required depending on plugin settings; unconfirmed users cannot log in if confirmation is enforced.
Request example
POST /api/auth/local/register
{
"username": "jdoe",
"email": "jdoe@example.com",
"password": "Str0ngPass!"
}
Response example
{
"jwt": "eyJhbGci...",
"user": {
"id": 1,
"username": "jdoe",
"email": "jdoe@example.com",
"confirmed": false,
"blocked": false
}
}
Authenticate (login)
- Method: POST
- URL:
/api/auth/local - Watch out for: The 'identifier' field accepts either username or email.
Request example
POST /api/auth/local
{
"identifier": "jdoe@example.com",
"password": "Str0ngPass!"
}
Response example
{
"jwt": "eyJhbGci...",
"user": {
"id": 1,
"username": "jdoe",
"email": "jdoe@example.com"
}
}
Get current authenticated user
- Method: GET
- URL:
/api/users/me - Watch out for: Returns the user associated with the JWT; requires a valid non-expired token.
Request example
GET /api/users/me
Authorization: Bearer <jwt>
Response example
{
"id": 1,
"username": "jdoe",
"email": "jdoe@example.com",
"confirmed": true,
"blocked": false,
"role": { "id": 1, "name": "Authenticated" }
}
List all users (admin/privileged)
- Method: GET
- URL:
/api/users - Watch out for: This endpoint returns an array (not a paginated object wrapper). The role must have 'find' permission granted in the Users & Permissions plugin settings.
Request example
GET /api/users?pagination[page]=1&pagination[pageSize]=25
Authorization: Bearer <admin-jwt-or-api-token>
Response example
[
{
"id": 1,
"username": "jdoe",
"email": "jdoe@example.com",
"confirmed": true,
"blocked": false
}
]
Get a single user
- Method: GET
- URL:
/api/users/:id - Watch out for: Requires 'findOne' permission. Public access is disabled by default.
Request example
GET /api/users/1
Authorization: Bearer <admin-jwt-or-api-token>
Response example
{
"id": 1,
"username": "jdoe",
"email": "jdoe@example.com",
"confirmed": true,
"blocked": false,
"role": { "id": 1, "name": "Authenticated" }
}
Create a user (admin)
- Method: POST
- URL:
/api/users - Watch out for: Password is stored hashed; role ID must correspond to an existing role.
Request example
POST /api/users
Authorization: Bearer <admin-jwt-or-api-token>
{
"username": "newuser",
"email": "new@example.com",
"password": "Pass123!",
"role": 1
}
Response example
{
"id": 2,
"username": "newuser",
"email": "new@example.com",
"confirmed": false,
"blocked": false
}
Update a user
- Method: PUT
- URL:
/api/users/:id - Watch out for: Uses PUT (full-style), but Strapi applies only the provided fields. Sending a new password here will hash and update it.
Request example
PUT /api/users/2
Authorization: Bearer <admin-jwt-or-api-token>
{
"blocked": true
}
Response example
{
"id": 2,
"username": "newuser",
"email": "new@example.com",
"blocked": true
}
Delete a user
- Method: DELETE
- URL:
/api/users/:id - Watch out for: Returns the deleted user object. Deletion is permanent; there is no soft-delete by default.
Request example
DELETE /api/users/2
Authorization: Bearer <admin-jwt-or-api-token>
Response example
{
"id": 2,
"username": "newuser",
"email": "new@example.com"
}
Rate limits, pagination, and events
Rate limits: Strapi does not enforce built-in rate limits at the application layer by default. Rate limiting can be added via middleware (e.g., koa-ratelimit) or at the infrastructure/reverse-proxy level. Strapi Cloud may apply platform-level limits not publicly documented per-tier.
Rate-limit headers: No
Retry-After header: No
Rate-limit notes: No official per-tier rate-limit figures are published in Strapi documentation as of the policy date.
Pagination method: offset
Default page size: 25
Max page size: 100
Pagination pointer: pagination[page] / pagination[pageSize] (page-based) or pagination[start] / pagination[limit] (offset-based)
Webhooks available: Yes
Webhook notes: Strapi supports webhooks configured in the Admin Panel under Settings > Webhooks. Webhooks fire on content lifecycle events (create, update, delete, publish, unpublish) for content types. There are no dedicated user-lifecycle webhook events (e.g., user.created) exposed natively through the Admin Panel UI.
Alternative event strategy: Custom lifecycle hooks can be implemented in Strapi's server-side code (src/extensions/users-permissions/content-types/user/lifecycles.js) to trigger external calls on user create/update/delete events.
Webhook events: entry.create, entry.update, entry.delete, entry.publish, entry.unpublish, media.create, media.update, media.delete
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: Enterprise
- Endpoint: Not documented
Limitations:
- Strapi does not provide a native SCIM 2.0 endpoint.
- SSO (SAML/OIDC) is available on the Enterprise Edition plan.
- Automated user provisioning via SCIM is not natively supported; custom middleware or third-party connectors would be required.
Common scenarios
Three automation scenarios are well-supported by the documented API surface.
First, programmatic user creation: POST /api/users with confirmed: true using a full-access API Token to create and activate an end-user in a single call, bypassing the email confirmation flow entirely
note this is intentional for machine-provisioned accounts but should not be used where email verification is a compliance requirement.
Second, account suspension: PUT /api/users/:id with { "blocked": true } sets the blocked field, causing subsequent /api/auth/local login attempts to return a 400;
critically, this does not invalidate JWTs already in circulation, so short token TTLs or a token denylist are required for immediate session termination.
Third, role assignment: the Users & Permissions plugin enforces a single role per user, so PUT /api/users/:userId with a new role ID replaces the existing role
there is no multi-role support natively, and role IDs must be retrieved first via GET /api/users-permissions/roles using an admin JWT.
Programmatically create and activate a new end-user
- Obtain an admin JWT via POST /api/auth/local with admin credentials, or use a full-access API Token.
- POST /api/users with { username, email, password, role, confirmed: true } to create the user and mark them as confirmed in one step.
- Verify creation by checking the returned user object's 'id' and 'confirmed' fields.
- Optionally assign a specific role by passing the role's numeric ID in the 'role' field.
Watch out for: Setting confirmed: true bypasses the email confirmation flow. If your Strapi instance enforces email confirmation, omitting this will prevent the user from logging in until they confirm.
Block (suspend) a user account
- Authenticate with an admin JWT or full-access API Token.
- Identify the user's numeric ID via GET /api/users?filters[email][$eq]=target@example.com.
- PUT /api/users/:id with { "blocked": true }.
- The user's subsequent login attempts to /api/auth/local will return a 400 error indicating the account is blocked.
Watch out for: Blocking does not invalidate existing JWTs already issued to the user. Implement token revocation logic (e.g., short TTLs or a token denylist) if immediate session termination is required.
Assign a role to an existing user
- Retrieve available roles via GET /api/users-permissions/roles (requires admin JWT).
- Note the numeric 'id' of the target role.
- PUT /api/users/:userId with { "role":
}. - Confirm the update by fetching GET /api/users/:userId and checking the 'role' relation.
Watch out for: The Users & Permissions plugin supports a single role per user. Assigning a new role replaces the existing one; there is no multi-role support natively.
Why building this yourself is a trap
The most consequential integration trap is the dual-database architecture: end-users at /api/users and admin panel users at /admin/users are stored separately, authenticated separately, and cannot be queried interchangeably. Any identity graph that conflates the two will produce incorrect access state.
A second trap is permission grant visibility: even authenticated users cannot reach /api/users endpoints unless the role's permissions are explicitly enabled in Admin Panel > Settings > Users & Permissions Plugin > Roles - this is a runtime configuration dependency, not a code-level one, and it silently returns 403 without a descriptive error.
SCIM 2.0 is not natively available on any plan; automated provisioning against an IdP requires custom middleware or a third-party connector, and there is no published roadmap for native SCIM support.
Finally, Strapi v4 and v5 have divergent plugin structures, so endpoint behavior and response shapes should always be validated against the installed version before building automation.
Automate Strapi workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.