Summary and recommendation
Procore's REST API (base URL: `https://api.procore.com/rest/v1.0`) uses OAuth 2.0 for authentication, supporting both Authorization Code and Client Credentials grant types. All requests require a `company_id` or `project_id` as a path parameter-there is no global user namespace. User payloads must be nested under a `user` key (e.g., `{"user": {...}}`); flat objects will be rejected.
There is no native SCIM 2.0 endpoint; all automated lifecycle management must be implemented against the REST API directly.
For teams building identity graph integrations, Procore's two-tier model (company directory + per-project membership) means a complete identity graph entry for any user requires data from both the company users endpoint and each relevant project users endpoint-these are not unified in a single response.
API quick reference
| Has user API | Yes |
| Auth method | OAuth 2.0 |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | Custom (ACV-based) |
Authentication
Auth method: OAuth 2.0
Setup steps
- Register a developer application at https://developers.procore.com/apps to obtain a client_id and client_secret.
- Choose an OAuth 2.0 grant type: Authorization Code (user-facing apps) or Client Credentials (service/machine-to-machine apps).
- For Authorization Code: redirect the user to https://login.procore.com/oauth/authorize with client_id, redirect_uri, response_type=code, and desired scopes.
- Exchange the authorization code for an access token via POST to https://login.procore.com/oauth/token.
- For Client Credentials: POST directly to https://login.procore.com/oauth/token with grant_type=client_credentials, client_id, and client_secret.
- Include the access token in all API requests as a Bearer token in the Authorization header.
- Access tokens expire; use the refresh_token (Authorization Code flow) or re-request (Client Credentials) to obtain a new token.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| read | Read access to Procore resources including users and directory data. | GET operations on users, company directory, project users |
| write | Write access to create and update Procore resources including users. | POST, PATCH, PUT, DELETE operations on users |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | integer | Unique Procore user ID. | system-generated | read-only | Used as path parameter for user-specific operations. |
| login | string | User's email address used as login credential. | required | optional | Must be unique across Procore. Acts as the primary identifier for login. |
| name | string | Full display name of the user. | required | optional | Combination of first_name and last_name in some contexts. |
| first_name | string | User's first name. | required | optional | |
| last_name | string | User's last name. | required | optional | |
| email_address | string | Primary email address. | required | optional | Typically same as login. |
| job_title | string | User's job title. | optional | optional | |
| business_phone | string | Business phone number. | optional | optional | |
| mobile_phone | string | Mobile phone number. | optional | optional | |
| is_active | boolean | Whether the user account is active. | optional (defaults true) | optional | Set to false to deactivate without deleting. |
| is_employee | boolean | Indicates if the user is an employee of the company. | optional | optional | |
| company_id | integer | ID of the company the user belongs to. | required (path param) | read-only | Passed as query/path parameter, not in body. |
| vendor_id | integer | ID of the vendor/company associated with the user. | optional | optional | Links user to a vendor record in the directory. |
| permission_template_id | integer | ID of the permission template to apply to the user. | optional | optional | Determines role-based access within projects. |
| created_at | datetime | Timestamp when the user was created. | system-generated | read-only | ISO 8601 format. |
| updated_at | datetime | Timestamp of last update. | system-generated | system-generated | ISO 8601 format. |
| avatar | string (URL) | URL to the user's profile avatar image. | optional | optional | |
| address | string | Street address. | optional | optional | |
| city | string | City. | optional | optional | |
| country_code | string | ISO country code. | optional | optional | e.g., US, CA, GB. |
Core endpoints
List Company Users
- Method: GET
- URL:
https://api.procore.com/rest/v1.0/companies/{company_id}/users - Watch out for: Returns users at the company level only. Project-level user membership requires a separate endpoint. Pagination defaults to 100 per page; use per_page up to 10000.
Request example
GET /rest/v1.0/companies/12345/users?page=1&per_page=100
Authorization: Bearer {access_token}
Response example
[
{
"id": 9876,
"login": "jane.doe@example.com",
"name": "Jane Doe",
"is_active": true,
"created_at": "2024-01-15T10:00:00Z"
}
]
Show Company User
- Method: GET
- URL:
https://api.procore.com/rest/v1.0/companies/{company_id}/users/{id} - Watch out for: Requires the exact Procore user ID (integer). Email-based lookup is not supported directly; use the list endpoint with filters.
Request example
GET /rest/v1.0/companies/12345/users/9876
Authorization: Bearer {access_token}
Response example
{
"id": 9876,
"login": "jane.doe@example.com",
"first_name": "Jane",
"last_name": "Doe",
"is_active": true,
"job_title": "Project Manager"
}
Create Company User
- Method: POST
- URL:
https://api.procore.com/rest/v1.0/companies/{company_id}/users - Watch out for: The user body must be nested under a 'user' key. Creating a user at the company level does not automatically add them to any project; project membership is a separate operation.
Request example
POST /rest/v1.0/companies/12345/users
Authorization: Bearer {access_token}
Content-Type: application/json
{"user":{"login":"john.smith@example.com","first_name":"John","last_name":"Smith","is_employee":true}}
Response example
{
"id": 9877,
"login": "john.smith@example.com",
"first_name": "John",
"last_name": "Smith",
"is_active": true,
"created_at": "2025-06-01T12:00:00Z"
}
Update Company User
- Method: PATCH
- URL:
https://api.procore.com/rest/v1.0/companies/{company_id}/users/{id} - Watch out for: Partial updates are supported. Setting is_active=false deactivates the user but does not remove them from projects. Full deletion is a separate operation.
Request example
PATCH /rest/v1.0/companies/12345/users/9876
Authorization: Bearer {access_token}
Content-Type: application/json
{"user":{"job_title":"Senior PM","is_active":false}}
Response example
{
"id": 9876,
"login": "jane.doe@example.com",
"job_title": "Senior PM",
"is_active": false,
"updated_at": "2025-06-01T13:00:00Z"
}
Delete Company User
- Method: DELETE
- URL:
https://api.procore.com/rest/v1.0/companies/{company_id}/users/{id} - Watch out for: Deletion may be blocked if the user has associated records (RFIs, submittals, etc.). Procore recommends deactivating (is_active=false) rather than deleting to preserve audit history.
Request example
DELETE /rest/v1.0/companies/12345/users/9876
Authorization: Bearer {access_token}
Response example
HTTP 204 No Content
List Project Users
- Method: GET
- URL:
https://api.procore.com/rest/v1.0/projects/{project_id}/users - Watch out for: Project-level user list is scoped to users added to that specific project. A company user must be explicitly added to a project to appear here.
Request example
GET /rest/v1.0/projects/55555/users?page=1&per_page=100
Authorization: Bearer {access_token}
Response example
[
{
"id": 9876,
"login": "jane.doe@example.com",
"name": "Jane Doe",
"permission_template_id": 101
}
]
Add User to Project
- Method: POST
- URL:
https://api.procore.com/rest/v1.0/projects/{project_id}/users - Watch out for: The user must already exist at the company level before being added to a project. permission_template_id controls project-level access; omitting it may result in no-access assignment.
Request example
POST /rest/v1.0/projects/55555/users
Authorization: Bearer {access_token}
Content-Type: application/json
{"user":{"id":9876,"permission_template_id":101}}
Response example
{
"id": 9876,
"name": "Jane Doe",
"permission_template_id": 101
}
Remove User from Project
- Method: DELETE
- URL:
https://api.procore.com/rest/v1.0/projects/{project_id}/users/{id} - Watch out for: Removes project membership only; the user remains in the company directory. Does not delete any project data created by the user.
Request example
DELETE /rest/v1.0/projects/55555/users/9876
Authorization: Bearer {access_token}
Response example
HTTP 204 No Content
Rate limits, pagination, and events
- Rate limits: Procore enforces rate limits per OAuth application (client_id). Limits apply on a per-minute rolling window basis.
- Rate-limit headers: Yes
- Retry-After header: Yes
- Rate-limit notes: When the rate limit is exceeded, the API returns HTTP 429. Response headers include X-Rate-Limit-Limit, X-Rate-Limit-Remaining, and X-Rate-Limit-Reset. Retry-After header is included on 429 responses. Limits are per OAuth application client_id, not per end user.
- Pagination method: offset
- Default page size: 100
- Max page size: 10000
- Pagination pointer: page and per_page
| Plan | Limit | Concurrent |
|---|---|---|
| Standard (all plans) | 3,600 requests per hour (approximately 60 requests/minute) per OAuth app | 0 |
- Webhooks available: Yes
- Webhook notes: Procore supports webhooks that fire on resource events. Webhooks are configured per company and can target HTTP endpoints. Configuration is available via the API or the Procore Developer Portal.
- Alternative event strategy: If webhooks are not suitable, poll the List Company Users endpoint with updated_after filter parameter to detect changes.
- Webhook events: create (user created), update (user updated), delete (user deleted), create (project user added), delete (project user removed)
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: Custom (ACV-based)
- Endpoint: Not documented
Limitations:
- Procore does not offer a native SCIM 2.0 endpoint as of the current documentation.
- User provisioning via Okta or Entra ID SSO is supported for authentication but not automated SCIM provisioning/deprovisioning.
- Users must be created manually in the Procore Directory tool or via the REST API.
- No automated lifecycle management (deprovisioning) through IdP SCIM push is available natively.
Common scenarios
Three scenarios cover the majority of lifecycle automation use cases. To provision a new employee, POST to `/rest/v1.
0/companies/{company_id}/userswithis_employee=true, capture the returned id, then POST to /rest/v1. 0/projects/{project_id}/userswith thatidand a validpermission_template_id`-omitting the second call leaves the user in the directory with zero project access.
To deactivate a departed employee, PATCH /rest/v1. 0/companies/{company_id}/users/{id} with {"user": {"is_active": false}}; this blocks login but does not remove the user from project member lists-project removal must be executed per-project via DELETE `/rest/v1.
0/projects/{project_id}/users/{id}. For bulk directory sync, GET /rest/v1.
0/companies/{company_id}/users? per_page=10000retrieves up to 10,000 records per page; use theX-Totalresponse header to determine whether pagination is needed, and useupdated_after` for incremental syncs where the endpoint version supports it.
Provision a new employee and add them to a project
- POST /rest/v1.0/companies/{company_id}/users with first_name, last_name, login (email), is_employee=true to create the company-level user.
- Capture the returned user id from the response.
- POST /rest/v1.0/projects/{project_id}/users with the user id and the appropriate permission_template_id to grant project access.
- Verify membership with GET /rest/v1.0/projects/{project_id}/users/{id}.
Watch out for: Skipping the project membership step leaves the user in the company directory with no project access. permission_template_id must be a valid template ID for that project.
Deactivate a departed employee across the company
- GET /rest/v1.0/companies/{company_id}/users?search={email} to locate the user and retrieve their id.
- PATCH /rest/v1.0/companies/{company_id}/users/{id} with {"user": {"is_active": false}} to deactivate the account.
- Optionally, DELETE /rest/v1.0/projects/{project_id}/users/{id} for each project to remove active project memberships if required.
- Verify deactivation with GET /rest/v1.0/companies/{company_id}/users/{id} and confirm is_active=false.
Watch out for: Deactivation prevents login but does not remove the user from project member lists automatically. Project removal must be done per-project. Avoid hard deletion to preserve audit trail.
Sync company users to an external directory (bulk read)
- GET /rest/v1.0/companies/{company_id}/users?page=1&per_page=10000 to retrieve all users in a single page (if under 10,000).
- If total users exceed 10,000 (check X-Total header), iterate pages incrementally using the page parameter.
- For incremental syncs, use the updated_after query parameter (if supported on the endpoint) or compare updated_at timestamps to detect changes.
- Map Procore user fields (login, first_name, last_name, is_active) to the target directory schema.
Watch out for: The updated_after filter availability varies by endpoint version; verify in the API reference. X-Total header provides total record count for pagination planning. Large exports near the 10,000 per_page limit may approach rate limit thresholds.
Why building this yourself is a trap
The most common integration failure point is treating company-level user creation as equivalent to access provisioning-it is not. A user created at the company level has no project visibility until explicitly added to each project with a permission_template_id.
Deletion is a second trap: DELETE on a user with associated records (RFIs, submittals, daily logs) may fail silently or leave orphaned references; prefer is_active=false via PATCH to preserve audit history.
Rate limits are enforced at 3,600 requests per hour per OAuth client_id; bulk operations must monitor X-Rate-Limit-Remaining headers and implement exponential backoff on HTTP 429 responses. Finally, email-based lookup is not a dedicated filter-the search query parameter returns partial matches, so downstream logic must validate exact login field matches before acting on results.
Automate Procore 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.