Summary and recommendation
Clover exposes a REST API at https://api.clover.com/v3/merchants/{mId} for programmatic employee lifecycle management. Authentication uses OAuth 2.0 authorization code flow; tokens are merchant-scoped, do not expire by default unless revoked, and do not cross between sandbox (sandbox.dev.clover.com) and production environments.
The EMPLOYEES_R and EMPLOYEES_W scopes are required for read and write operations respectively; MERCHANT_R is needed for role metadata.
There is no SCIM 2.0 implementation on any plan - all provisioning must go through the proprietary REST API, which means any identity graph built on top of Clover requires custom mapping between Clover's employee id (UUID) and your IdP or HR system's user identifier.
API quick reference
| Has user API | Yes |
| Auth method | OAuth 2.0 (authorization code flow); access token passed as Bearer token in Authorization header or as token query parameter |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | Not available on any plan |
Authentication
Auth method: OAuth 2.0 (authorization code flow); access token passed as Bearer token in Authorization header or as token query parameter
Setup steps
- Register a developer account at https://sandbox.dev.clover.com and create an app in the Developer Dashboard.
- Configure your app's redirect URI and requested permissions (scopes) in the app settings.
- Direct the merchant to the Clover OAuth authorization URL: https://sandbox.dev.clover.com/oauth/authorize?client_id={APP_ID}&redirect_uri={REDIRECT_URI}
- After merchant approval, exchange the returned authorization code for an access token via POST to https://sandbox.dev.clover.com/oauth/token.
- Store the returned access token and use it as a Bearer token in subsequent API requests. Tokens are merchant-scoped and do not expire by default unless revoked.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| EMPLOYEES_R | Read employee records for the merchant. | GET /employees, GET /employees/{empId} |
| EMPLOYEES_W | Create, update, and delete employee records. | POST /employees, PUT /employees/{empId}, DELETE /employees/{empId} |
| MERCHANT_R | Read merchant-level information including roles. | GET /merchants/{mId} and related merchant metadata |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | string | Unique employee identifier (UUID). | system-generated | immutable | Used as path parameter in employee-specific endpoints. |
| name | string | Full display name of the employee. | required | optional | |
| nickname | string | Short name shown on receipts and POS UI. | optional | optional | |
| string | Employee email address. | optional | optional | Used for Clover account invitation if employee does not already have one. | |
| pin | string | Numeric PIN used to clock in/out and authenticate at POS. | optional | optional | Write-only; not returned in GET responses. |
| role | string (enum) | Predefined role: OWNER, ADMIN, MANAGER, EMPLOYEE. | optional (defaults to EMPLOYEE) | optional | OWNER role cannot be assigned via API. |
| roles | object (reference) | Reference to a custom role object if custom roles are used. | optional | optional | Custom roles must be created separately via /roles endpoint. |
| isOwner | boolean | Indicates if the employee is the merchant owner. | system-set | immutable | |
| claimedTime | integer (epoch ms) | Timestamp when the employee claimed their Clover account. | system-set | immutable | |
| deletedTime | integer (epoch ms) | Soft-delete timestamp; non-null means employee is deleted. | system-set | system-set | |
| customId | string | Merchant-defined external identifier for the employee. | optional | optional | |
| inviteSent | boolean | Whether a Clover account invitation email has been sent. | system-set | read-only |
Core endpoints
List all employees
- Method: GET
- URL:
https://api.clover.com/v3/merchants/{mId}/employees - Watch out for: Deleted employees are excluded by default. Use ?filter=deletedTime>0 to include them.
Request example
GET /v3/merchants/{mId}/employees?limit=100&offset=0
Authorization: Bearer {access_token}
Response example
{
"elements": [
{"id":"ABC123","name":"Jane Doe","role":"EMPLOYEE","email":"jane@example.com"}
],
"href": "https://api.clover.com/v3/merchants/{mId}/employees"
}
Get single employee
- Method: GET
- URL:
https://api.clover.com/v3/merchants/{mId}/employees/{empId} - Watch out for: Returns 404 if employee was hard-deleted or belongs to a different merchant.
Request example
GET /v3/merchants/{mId}/employees/{empId}
Authorization: Bearer {access_token}
Response example
{
"id": "ABC123",
"name": "Jane Doe",
"role": "EMPLOYEE",
"email": "jane@example.com",
"isOwner": false
}
Create employee
- Method: POST
- URL:
https://api.clover.com/v3/merchants/{mId}/employees - Watch out for: PIN is accepted on create but never returned in subsequent GET calls. Email triggers an invite only if the employee does not already have a Clover account.
Request example
POST /v3/merchants/{mId}/employees
Authorization: Bearer {access_token}
Content-Type: application/json
{"name":"John Smith","email":"john@example.com","role":"EMPLOYEE","pin":"1234"}
Response example
{
"id": "XYZ789",
"name": "John Smith",
"role": "EMPLOYEE",
"email": "john@example.com"
}
Update employee
- Method: PUT
- URL:
https://api.clover.com/v3/merchants/{mId}/employees/{empId} - Watch out for: Clover uses PUT (full-replace semantics) not PATCH. Omitting optional fields may clear them depending on API version.
Request example
PUT /v3/merchants/{mId}/employees/{empId}
Authorization: Bearer {access_token}
Content-Type: application/json
{"name":"John A. Smith","role":"MANAGER"}
Response example
{
"id": "XYZ789",
"name": "John A. Smith",
"role": "MANAGER"
}
Delete employee
- Method: DELETE
- URL:
https://api.clover.com/v3/merchants/{mId}/employees/{empId} - Watch out for: Cannot delete the merchant owner (isOwner=true). Returns 400 if attempted.
Request example
DELETE /v3/merchants/{mId}/employees/{empId}
Authorization: Bearer {access_token}
Response example
HTTP 200 OK
(empty body)
List roles
- Method: GET
- URL:
https://api.clover.com/v3/merchants/{mId}/roles - Watch out for: Custom roles are merchant-specific. The systemRole field maps to the base permission tier.
Request example
GET /v3/merchants/{mId}/roles
Authorization: Bearer {access_token}
Response example
{
"elements": [
{"id":"ROLE1","name":"Cashier","systemRole":"EMPLOYEE"}
]
}
Get current authenticated employee
- Method: GET
- URL:
https://api.clover.com/v3/merchants/{mId}/employees/current - Watch out for: Returns the employee associated with the OAuth token, not necessarily an admin. Useful for identity verification.
Request example
GET /v3/merchants/{mId}/employees/current
Authorization: Bearer {access_token}
Response example
{
"id": "ABC123",
"name": "Jane Doe",
"role": "ADMIN",
"isOwner": false
}
Rate limits, pagination, and events
- Rate limits: Clover does not publish explicit per-plan rate limit numbers in official documentation. Throttling is enforced server-side; excessive requests result in HTTP 429 responses.
- Rate-limit headers: No
- Retry-After header: No
- Rate-limit notes: Official docs do not specify header names or numeric limits. Implement exponential backoff on HTTP 429.
- Pagination method: offset
- Default page size: 100
- Max page size: 1000
- Pagination pointer: offset and limit query parameters; e.g., ?limit=100&offset=0
| Plan | Limit | Concurrent |
|---|---|---|
| All plans (sandbox) | Not publicly documented; sandbox is more restrictive than production | 0 |
| All plans (production) | Not publicly documented | 0 |
- Webhooks available: Yes
- Webhook notes: Clover supports webhooks for merchant data change events. Apps register a webhook URL in the Developer Dashboard. Clover sends HTTP POST notifications when subscribed entities change.
- Alternative event strategy: Poll GET /employees with a timestamp filter (modifiedTime) for environments where webhooks are not feasible.
- Webhook events: EMPLOYEE_CREATE, EMPLOYEE_UPDATE, EMPLOYEE_DELETE
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: Not available on any plan
- Endpoint: Not documented
Limitations:
- Clover does not implement SCIM 2.0.
- No IdP (Okta, Entra, Google Workspace, OneLogin) integration documented.
- Employee provisioning must be done via the proprietary REST API.
Common scenarios
Three core automation scenarios are well-supported by the API. For provisioning, POST /v3/merchants/{mId}/employees with name, email, pin, and a roles:{id} reference fetched from GET /v3/merchants/{mId}/roles; omitting the roles object silently defaults the employee to the EMPLOYEE system role.
For roster sync, paginate GET /employees with limit=1000 and offset increments, then use the modifiedTime filter for incremental runs - deleted employees are excluded from default responses and must be queried separately with filter=deletedTime>0 to detect deprovisioned users.
For deprovisioning, confirm isOwner=false before calling DELETE /employees/{empId}; the API returns HTTP 400 if you attempt to delete the merchant owner, and deletion is immediate and irreversible with no archive state.
Provision a new employee with a custom role
- Obtain a merchant-scoped OAuth 2.0 access token via the authorization code flow.
- GET /v3/merchants/{mId}/roles to retrieve available custom role IDs.
- POST /v3/merchants/{mId}/employees with name, email, pin, and roles:{id} body to create the employee.
- Clover automatically sends an invitation email to the provided address if the employee has no existing Clover account.
Watch out for: If the roles object is omitted, the employee defaults to the EMPLOYEE system role. Custom role IDs must be fetched first; they are not predictable.
Sync employee roster to an external HR system
- GET /v3/merchants/{mId}/employees?limit=1000&offset=0 to retrieve all active employees.
- Paginate using offset increments until the elements array is empty.
- Map Clover employee id to the external system's user identifier.
- For incremental sync, filter by modifiedTime: GET /v3/merchants/{mId}/employees?filter=modifiedTime>{lastSyncEpochMs}
Watch out for: Deleted employees are excluded from default responses. Query with filter=deletedTime>0 separately to detect deprovisioned users.
Deprovision a departing employee
- GET /v3/merchants/{mId}/employees to find the employee's id by email or name.
- Confirm isOwner=false before proceeding (owner cannot be deleted via API).
- DELETE /v3/merchants/{mId}/employees/{empId} to remove the employee.
- Verify removal by confirming HTTP 200 response and re-querying the employee ID (expect 404).
Watch out for: Deletion is immediate and cannot be undone via the API. The employee loses POS access instantly but any in-progress sessions on hardware may not terminate until the device syncs.
Why building this yourself is a trap
Several non-obvious behaviors create integration risk. PUT semantics are used for updates - not PATCH - so omitting optional fields on an update call may silently clear them depending on API version.
PIN values are write-only: accepted on POST but never returned in any GET response, which complicates any sync logic that tries to validate existing PIN state. Rate limits are not documented numerically; the API returns HTTP 429 on throttle with no Retry-After header, so exponential backoff must be implemented defensively.
Webhook delivery for EMPLOYEE_CREATE, EMPLOYEE_UPDATE, and EMPLOYEE_DELETE events is not guaranteed - Clover's own documentation recommends reconciling via polling against modifiedTime if webhook events are missed. Finally, multi-merchant integrations must manage one OAuth token per merchant; there is no org-level token or tenant-scoped credential model.
Automate Clover 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.