Stitchflow
Spree Commerce logo

Spree Commerce User Management API Guide

API workflow

How to automate user lifecycle operations through APIs with caveats that matter in production.

UpdatedMar 16, 2026

Summary and recommendation

Spree's Platform API (`/api/v2/platform/users`) provides full CRUD over user accounts and is the correct integration surface for automated provisioning.

Authentication uses OAuth 2.0 via the Doorkeeper gem;

all Platform API calls require a `client_credentials` grant with `admin` scope.

The API follows JSON:API spec, uses offset-based pagination (`page`/`per_page`, max 500), and has no built-in rate limiting - throttling must be added at the infrastructure layer via `rack-attack` or a reverse proxy.

For teams building identity graph pipelines, the `public_metadata` and `private_metadata` fields on the user object are the designated extension points for storing external system identifiers (e.g., IdP subject IDs, CRM references) without schema changes.

API quick reference

Has user APIYes
Auth methodOAuth 2.0 Bearer Token (doorkeeper gem)
Base URLOfficial docs
SCIM availableNo

Authentication

Auth method: OAuth 2.0 Bearer Token (doorkeeper gem)

Setup steps

  1. Install and configure the doorkeeper gem (included in spree_auth_devise by default).
  2. Create an OAuth application via the Spree admin panel at /admin/oauth_applications or via Rails console: Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: '', scopes: 'admin').
  3. Obtain a client_credentials token: POST /api/v2/oauth/token with grant_type=client_credentials, client_id, and client_secret.
  4. Include the token in all requests as: Authorization: Bearer .
  5. For user-scoped (storefront) tokens use grant_type=password with username and password.

Required scopes

Scope Description Required for
admin Full access to Platform API resources including user management CRUD. All Platform API /api/v2/platform/* endpoints including users.
api_v2 General Storefront API access for the authenticated user's own data. Storefront API /api/v2/storefront/* endpoints (own account only).

User object / data model

Field Type Description On create On update Notes
id integer Unique user identifier. auto-assigned read-only
email string User's email address; used as login. required optional Must be unique per store.
password string Plain-text password (write-only). required optional Never returned in responses.
password_confirmation string Must match password on create/update. required optional Write-only.
first_name string User's first name. optional optional
last_name string User's last name. optional optional
phone string User's phone number. optional optional
spree_role_ids array[integer] Array of Spree::Role IDs assigned to the user. optional optional Use role name 'admin' to grant admin access.
ship_address_id integer Default shipping address ID. optional optional
bill_address_id integer Default billing address ID. optional optional
created_at datetime ISO 8601 timestamp of account creation. auto-assigned read-only
updated_at datetime ISO 8601 timestamp of last update. auto-assigned auto-assigned
completed_orders_count integer Number of completed orders. read-only read-only
store_credits_total decimal Total store credit balance. read-only read-only
public_metadata object Arbitrary key-value metadata visible to storefront. optional optional
private_metadata object Arbitrary key-value metadata visible to admins only. optional optional

Core endpoints

List Users

  • Method: GET
  • URL: /api/v2/platform/users
  • Watch out for: Requires admin-scoped OAuth token. Storefront tokens only return the authenticated user's own data.

Request example

GET /api/v2/platform/users?page=1&per_page=25
Authorization: Bearer <admin_token>

Response example

{
  "data": [{"id":"1","type":"user","attributes":{"email":"alice@example.com",...}}],
  "meta": {"count":1,"total_count":1,"total_pages":1},
  "links": {"self":"...","next":null,"prev":null}
}

Get User

  • Method: GET
  • URL: /api/v2/platform/users/{id}
  • Watch out for: Returns 404 if user belongs to a different store in a multi-store setup.

Request example

GET /api/v2/platform/users/42
Authorization: Bearer <admin_token>

Response example

{
  "data": {
    "id": "42",
    "type": "user",
    "attributes": {"email":"bob@example.com","first_name":"Bob"}
  }
}

Create User

  • Method: POST
  • URL: /api/v2/platform/users
  • Watch out for: password and password_confirmation are required. Duplicate email returns HTTP 422 with validation errors.

Request example

POST /api/v2/platform/users
Authorization: Bearer <admin_token>
Content-Type: application/json

{"user":{"email":"new@example.com","password":"s3cr3t","password_confirmation":"s3cr3t"}}

Response example

{
  "data": {
    "id": "99",
    "type": "user",
    "attributes": {"email":"new@example.com","created_at":"2024-01-01T00:00:00.000Z"}
  }
}

Update User

  • Method: PATCH
  • URL: /api/v2/platform/users/{id}
  • Watch out for: Sending spree_role_ids replaces all existing roles; omitting the field leaves roles unchanged.

Request example

PATCH /api/v2/platform/users/99
Authorization: Bearer <admin_token>
Content-Type: application/json

{"user":{"first_name":"Alice","spree_role_ids":[1]}}

Response example

{
  "data": {
    "id": "99",
    "type": "user",
    "attributes": {"first_name":"Alice","email":"new@example.com"}
  }
}

Delete User

  • Method: DELETE
  • URL: /api/v2/platform/users/{id}
  • Watch out for: Spree soft-deletes users by default (paranoia/discard gem). The record is not permanently removed unless explicitly purged.

Request example

DELETE /api/v2/platform/users/99
Authorization: Bearer <admin_token>

Response example

HTTP 204 No Content

Get Current Storefront User

  • Method: GET
  • URL: /api/v2/storefront/account
  • Watch out for: Uses a password-grant user token, not a client_credentials admin token.

Request example

GET /api/v2/storefront/account
Authorization: Bearer <user_token>

Response example

{
  "data": {
    "id": "42",
    "type": "user",
    "attributes": {"email":"bob@example.com","first_name":"Bob"}
  }
}

Update Current Storefront User

  • Method: PATCH
  • URL: /api/v2/storefront/account
  • Watch out for: Users cannot update their own roles via this endpoint; role changes require admin token.

Request example

PATCH /api/v2/storefront/account
Authorization: Bearer <user_token>
Content-Type: application/json

{"user":{"first_name":"Bob","phone":"555-1234"}}

Response example

{
  "data": {
    "id": "42",
    "type": "user",
    "attributes": {"first_name":"Bob","phone":"555-1234"}
  }
}

Obtain OAuth Token

  • Method: POST
  • URL: /api/v2/oauth/token
  • Watch out for: client_credentials grant yields an admin-level token. Use grant_type=password with username/password for user-scoped tokens.

Request example

POST /api/v2/oauth/token
Content-Type: application/json

{"grant_type":"client_credentials","client_id":"APP_ID","client_secret":"APP_SECRET"}

Response example

{
  "access_token": "abc123...",
  "token_type": "Bearer",
  "expires_in": 7200,
  "created_at": 1700000000
}

Rate limits, pagination, and events

  • Rate limits: Spree is self-hosted open-source software. No built-in rate limiting is enforced by the framework itself. Rate limiting must be implemented by the deployer via Rack middleware (e.g., rack-attack gem) or an upstream reverse proxy/CDN.

  • Rate-limit headers: No

  • Retry-After header: No

  • Rate-limit notes: Official docs do not specify any default rate limits or rate-limit response headers. Operators must configure rack-attack or equivalent independently.

  • Pagination method: offset

  • Default page size: 25

  • Max page size: 500

  • Pagination pointer: page / per_page

  • Webhooks available: Yes

  • Webhook notes: Spree supports webhooks natively from v4.3+. Webhook subscribers can be configured via the admin UI or Platform API at /api/v2/platform/webhooks/subscribers. Events are dispatched asynchronously via ActiveJob.

  • Alternative event strategy: For older Spree versions without native webhooks, use ActiveRecord callbacks or the spree_webhooks community gem.

  • Webhook events: order.completed, order.canceled, product.created, product.updated, product.deleted, user.created, user.updated, user.deleted

SCIM API status

  • SCIM available: No
  • SCIM version: Not documented
  • Plan required: Not documented
  • Endpoint: Not documented

Limitations:

  • Spree has no native SCIM 2.0 implementation.
  • As a self-hosted Rails application, SCIM provisioning must be built custom (e.g., using the scimitar or scim_rails gem).
  • No official Okta, Entra ID, or Google Workspace SCIM connector exists for Spree.

Common scenarios

Three scenarios cover the primary provisioning lifecycle.

To provision an admin: obtain a client_credentials token, resolve the admin role ID via GET /api/v2/platform/roles?filter[name]=admin, then POST /api/v2/platform/users with spree_role_ids set - omitting that field creates a Customer, not an Admin.

To deprovision: DELETE /api/v2/platform/users/{id} returns HTTP 204 and soft-deletes the record via the paranoia/discard gem;

the user disappears from list responses but remains in the database and requires really_destroy! for a hard delete.

To sync external identity data: PATCH /api/v2/platform/users/{id} with private_metadata for internal attributes and public_metadata for storefront-visible fields

note that public_metadata is readable by the authenticated user via the Storefront API, so sensitive identifiers belong in private_metadata only.

Provision a new admin user via Platform API

  1. POST /api/v2/oauth/token with grant_type=client_credentials to obtain an admin Bearer token.
  2. Retrieve the admin role ID: GET /api/v2/platform/roles?filter[name]=admin to find the role ID (typically 1).
  3. POST /api/v2/platform/users with body {"user":{"email":"admin@co.com","password":"...","password_confirmation":"...","spree_role_ids":[1]}}.
  4. Confirm HTTP 201 response and store the returned user id for future management.

Watch out for: If spree_role_ids is omitted, the user is created as a regular customer with no admin access.

Deactivate (soft-delete) a user

  1. Obtain admin Bearer token via POST /api/v2/oauth/token.
  2. Identify the user: GET /api/v2/platform/users?filter[email]=target@example.com.
  3. DELETE /api/v2/platform/users/{id} - returns HTTP 204.
  4. Verify the user no longer appears in GET /api/v2/platform/users list responses (soft-deleted records are filtered out).

Watch out for: Soft-deleted users are not permanently removed. To hard-delete, execute Spree::User.with_deleted.find(id).really_destroy! in the Rails console or a custom admin action.

Update user metadata for external system integration

  1. Obtain admin Bearer token.
  2. PATCH /api/v2/platform/users/{id} with body {"user":{"public_metadata":{"crm_id":"CRM-001"},"private_metadata":{"internal_tier":"gold"}}}.
  3. Confirm updated_at changes in the response to verify the write succeeded.
  4. Use GET /api/v2/platform/users/{id} to read back and confirm metadata fields.

Watch out for: public_metadata is visible via the Storefront API to the authenticated user; do not store sensitive data there. private_metadata is admin-only.

Why building this yourself is a trap

Several behaviors will cause silent failures if not handled explicitly. spree_role_ids on a PATCH request is a full replacement - sending only the new role ID strips all existing roles; omit the field entirely when roles are not changing.

Spree is multi-store aware: user records and API responses are scoped to the store resolved from the request's Host header, so cross-store lookups will return HTTP 404 rather than a cross-store result.

Token expiry defaults to 7200 seconds with no automatic refresh unless the refresh_token grant is explicitly configured - unhandled expiry will produce 401s mid-pipeline. Finally, there is no native SCIM 2.0 endpoint;

integrations with Okta, Entra ID, or Google Workspace require a custom SCIM layer built with a gem such as scimitar or scim_rails, and no official connector exists.

Teams using an orchestration layer like an MCP server with 60+ deep IT/identity integrations should account for the absence of SCIM when mapping Spree into a broader identity graph.

Automate Spree Commerce 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.

Every app coverage, including apps without APIs
60+ app integrations plus browser automation for apps without APIs
IT graph reconciliation across apps and your IdP
Less than a week to launch, maintained as APIs and admin consoles change
SOC 2 Type II. ~2 hours of your team's time

UpdatedMar 16, 2026

* Details sourced from official product documentation and admin references.

Keep exploring

Related apps

Abnormal Security logo

Abnormal Security

API Only
AutomationAPI only
Last updatedMar 2026

Abnormal Security is an enterprise email security platform focused on detecting and investigating threats such as phishing, account takeover (ATO), and vendor email compromise. It does not support SCIM provisioning, which means every app in your stack

ActiveCampaign logo

ActiveCampaign

API Only
AutomationAPI only
Last updatedFeb 2026

ActiveCampaign uses a group-based permission model: every user belongs to exactly one group, and all feature-area access (Contacts, Campaigns, Automations, Deals, Reports, Templates) is configured at the group level, not per individual. The default Adm

ADP logo

ADP

API Only
AutomationAPI only
Last updatedFeb 2026

ADP Workforce Now is a mid-market to enterprise HCM platform that serves as the HR source of record for employee data — payroll, benefits, time, and talent. User access is governed by a hybrid permission model: predefined security roles (Security Maste