Summary and recommendation
Deel's REST API (base URL: https://api.letsdeel.com/rest/v2) uses Bearer token auth issued from Settings → Integrations → API Tokens, with OAuth 2.0 client credentials available for partner integrations. The critical architectural fact for any identity graph integration is that Deel's data model is contract-centric: worker creation, termination, and most lifecycle mutations operate on /contracts, not /people.
A person record is a side-effect of contract creation; a single person_id can be associated with multiple contract_ids (e.g., a contractor engagement followed by an EOR contract), and these identifiers are distinct and must both be stored.
SCIM 2.0 is available at https://app.deel.com/scim/v2 on Enterprise plans only, requires SAML 2.0 SSO as a prerequisite, and is officially supported for Okta, JumpCloud, and Microsoft Entra ID.
API quick reference
| Has user API | Yes |
| Auth method | Bearer token (API key issued from Deel dashboard; OAuth 2.0 client credentials also documented for partner integrations) |
| Base URL | Official docs |
| SCIM available | Yes |
| SCIM plan required | Enterprise |
Authentication
Auth method: Bearer token (API key issued from Deel dashboard; OAuth 2.0 client credentials also documented for partner integrations)
Setup steps
- Log in to the Deel dashboard as an admin.
- Navigate to Settings → Integrations → API Tokens.
- Generate a new API token and copy the value immediately (shown once).
- Pass the token in the Authorization header: 'Authorization: Bearer
'. - For OAuth 2.0 partner flows: register an app in the Deel developer portal to obtain client_id and client_secret, then POST to https://api.letsdeel.com/oauth/token with grant_type=client_credentials.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| people:read | Read worker/employee profile data. | List and retrieve people/workers |
| people:write | Create and update worker profiles. | Invite or update workers |
| contracts:read | Read contract details associated with workers. | Fetching contract-linked user data |
| webhooks:write | Register and manage webhook endpoints. | Webhook subscription management |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | string (UUID) | Unique Deel identifier for the person. | system-generated | immutable | Use this ID for all subsequent API calls. |
| first_name | string | Legal first name. | required | optional | |
| last_name | string | Legal last name. | required | optional | |
| string | Primary work email address. | required | optional | Must be unique within the organization. | |
| nationality | string (ISO 3166-1 alpha-2) | Country of nationality. | optional | optional | |
| country_of_residence | string (ISO 3166-1 alpha-2) | Country where the worker resides. | optional | optional | |
| job_title | string | Worker's job title. | optional | optional | |
| department | string | Department or team name. | optional | optional | |
| manager_id | string (UUID) | Deel ID of the direct manager. | optional | optional | |
| employment_type | enum | Type of engagement: employee, contractor, eor. | required | read-only after creation | Determines which contract flow applies. |
| start_date | string (ISO 8601 date) | Contract or employment start date. | required | optional | |
| end_date | string (ISO 8601 date) | Contract end date (fixed-term). | optional | optional | Null for indefinite contracts. |
| status | enum | Worker status: active, inactive, pending. | system-set | via termination/activation endpoints | |
| currency | string (ISO 4217) | Payment currency. | required | optional | |
| salary_amount | number | Gross salary or rate amount. | required | optional | Interpretation depends on pay_frequency. |
| pay_frequency | enum | Payment frequency: monthly, biweekly, weekly. | required | optional | |
| timezone | string (IANA tz) | Worker's timezone. | optional | optional | |
| phone_number | string | Contact phone number. | optional | optional | |
| custom_fields | object | Key-value map of organization-defined custom attributes. | optional | optional | Keys must be pre-configured in Deel settings. |
| created_at | string (ISO 8601 datetime) | Timestamp of record creation. | system-generated | immutable |
Core endpoints
List people (workers)
- Method: GET
- URL:
https://api.letsdeel.com/rest/v2/people - Watch out for: Returns all worker types (employees, contractors, EOR). Filter by employment_type query param if needed.
Request example
GET /rest/v2/people?limit=20&offset=0
Authorization: Bearer <token>
Response example
{
"data": [
{"id": "abc-123", "first_name": "Jane", "last_name": "Doe", "email": "jane@acme.com", "status": "active"}
],
"page": {"total": 150, "limit": 20, "offset": 0}
}
Get a single person
- Method: GET
- URL:
https://api.letsdeel.com/rest/v2/people/{person_id} - Watch out for: person_id is the Deel UUID, not the contract ID. These are distinct identifiers.
Request example
GET /rest/v2/people/abc-123
Authorization: Bearer <token>
Response example
{
"data": {
"id": "abc-123",
"first_name": "Jane",
"email": "jane@acme.com",
"status": "active",
"job_title": "Engineer"
}
}
Invite a new worker
- Method: POST
- URL:
https://api.letsdeel.com/rest/v2/contracts - Watch out for: Worker creation is contract-centric in Deel. A person record is created as a side-effect of contract creation, not via a standalone /people POST.
Request example
POST /rest/v2/contracts
Authorization: Bearer <token>
Content-Type: application/json
{
"type": "employee",
"worker_email": "new@acme.com",
"first_name": "John",
"last_name": "Smith",
"start_date": "2025-06-01"
}
Response example
{
"data": {
"id": "contract-456",
"status": "pending",
"worker_email": "new@acme.com"
}
}
Update a person's profile
- Method: PATCH
- URL:
https://api.letsdeel.com/rest/v2/people/{person_id} - Watch out for: Not all fields are patchable post-contract-signing. Employment type and salary changes may require a contract amendment flow.
Request example
PATCH /rest/v2/people/abc-123
Authorization: Bearer <token>
Content-Type: application/json
{
"job_title": "Senior Engineer",
"department": "Platform"
}
Response example
{
"data": {
"id": "abc-123",
"job_title": "Senior Engineer",
"department": "Platform"
}
}
Terminate a worker
- Method: POST
- URL:
https://api.letsdeel.com/rest/v2/contracts/{contract_id}/terminations - Watch out for: Termination is contract-scoped. A person with multiple contracts requires separate termination calls per contract.
Request example
POST /rest/v2/contracts/contract-456/terminations
Authorization: Bearer <token>
Content-Type: application/json
{
"termination_date": "2025-07-31",
"reason": "resignation"
}
Response example
{
"data": {
"contract_id": "contract-456",
"status": "terminated",
"termination_date": "2025-07-31"
}
}
List contracts
- Method: GET
- URL:
https://api.letsdeel.com/rest/v2/contracts - Watch out for: Contract IDs and person IDs are different. Store both if you need to cross-reference.
Request example
GET /rest/v2/contracts?status=active&limit=50
Authorization: Bearer <token>
Response example
{
"data": [
{"id": "contract-456", "type": "employee", "status": "active", "worker_email": "jane@acme.com"}
],
"page": {"total": 80, "limit": 50, "offset": 0}
}
List webhook subscriptions
- Method: GET
- URL:
https://api.letsdeel.com/rest/v2/webhooks - Watch out for: Webhook secrets must be stored at creation time; they are not retrievable afterward.
Request example
GET /rest/v2/webhooks
Authorization: Bearer <token>
Response example
{
"data": [
{"id": "wh-789", "url": "https://yourapp.com/hook", "events": ["contract.created"]}
]
}
Create a webhook subscription
- Method: POST
- URL:
https://api.letsdeel.com/rest/v2/webhooks - Watch out for: The 'secret' field is only returned on creation. Use it to verify HMAC signatures on incoming payloads.
Request example
POST /rest/v2/webhooks
Authorization: Bearer <token>
Content-Type: application/json
{
"url": "https://yourapp.com/hook",
"events": ["contract.created", "contract.terminated"]
}
Response example
{
"data": {
"id": "wh-789",
"secret": "whsec_abc123",
"events": ["contract.created", "contract.terminated"]
}
}
Rate limits, pagination, and events
- Rate limits: Deel enforces per-token rate limits. Official documentation does not publish exact numeric limits publicly; limits are enforced and communicated via response headers.
- Rate-limit headers: Yes
- Retry-After header: Yes
- Rate-limit notes: When rate limited, Deel returns HTTP 429. Inspect X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After headers. Exact limits are not published in official docs as of this research.
- Pagination method: offset
- Default page size: 10
- Max page size: 100
- Pagination pointer: limit / offset
| Plan | Limit | Concurrent |
|---|---|---|
| Standard API token | Not publicly documented; contact Deel for specifics | 0 |
- Webhooks available: Yes
- Webhook notes: Deel supports outbound webhooks registered via the API or dashboard. Payloads are signed with HMAC-SHA256 using a secret provided at subscription creation.
- Alternative event strategy: Poll GET /rest/v2/people or GET /rest/v2/contracts with updated_since filter for systems that cannot receive webhooks.
- Webhook events: contract.created, contract.updated, contract.terminated, contract.signed, payment.completed, worker.profile_updated, offboarding.initiated
SCIM API status
SCIM available: Yes
SCIM version: 2.0
Plan required: Enterprise
Endpoint: https://app.deel.com/scim/v2
Supported operations: GET /Users, GET /Users/{id}, POST /Users, PUT /Users/{id}, PATCH /Users/{id}, DELETE /Users/{id}, GET /Groups, POST /Groups, PATCH /Groups/{id}, DELETE /Groups/{id}
Limitations:
- Requires SAML 2.0 SSO to be configured before SCIM can be enabled.
- Supported IdPs: Okta, JumpCloud, Microsoft Entra ID (Azure AD). Google Workspace and OneLogin not officially documented.
- SCIM provisioning creates pending worker invitations; workers must still complete onboarding in Deel.
- Deprovisioning via SCIM deactivates the Deel account but does not automatically terminate contracts.
- Group push maps to Deel departments; department names must match exactly.
- Available on Enterprise plan only; not available on free HR or standard paid tiers.
Common scenarios
Three integration patterns cover the majority of production use cases. First, contractor onboarding: POST to /rest/v2/contracts with type=contractor; the returned response contains both contract_id and person_id, both of which must be persisted for downstream identity graph resolution.
The contract status stays 'pending' until the worker completes KYC and banking setup-poll GET /rest/v2/contracts/{contract_id} or subscribe to the contract. signed webhook to gate downstream provisioning on confirmed activation.
Second, directory sync: paginate GET /rest/v2/people with limit=100/offset=0, storing id, email, status, job_title, department, and manager_id per record; subscribe to worker. profile_updated and contract.
terminated webhooks for real-time delta updates, since updated_since filtering availability is not guaranteed and full-page polling is expensive at scale.
Third, SCIM deprovision (Enterprise): IdP deactivation sends PATCH /Users/{id} with active=false to Deel's SCIM endpoint, which disables login access only-contract termination must be triggered separately via POST /rest/v2/contracts/{contract_id}/terminations, as SCIM deactivation does not cascade to payroll or contract obligations.
Onboard a new contractor
- POST /rest/v2/contracts with type=contractor, worker_email, first_name, last_name, start_date, currency, and rate fields.
- Store the returned contract_id and the associated person_id from the response.
- Poll GET /rest/v2/contracts/{contract_id} or listen for the contract.signed webhook to confirm the worker has completed onboarding.
- Once active, use GET /rest/v2/people/{person_id} to retrieve the full profile.
Watch out for: The worker receives an email invitation and must self-complete KYC/banking details. The contract status remains 'pending' until they do.
Sync Deel workers to an external directory via REST API
- GET /rest/v2/people?limit=100&offset=0 and paginate through all pages.
- For each person, store id, email, status, job_title, department, and manager_id.
- On subsequent syncs, use an updated_since query parameter (if supported) or compare stored records to detect changes.
- Subscribe to worker.profile_updated and contract.terminated webhooks to receive real-time delta updates.
Watch out for: If updated_since filtering is unavailable, full-page polling is required, which is expensive for large orgs. Webhooks are strongly preferred for change detection.
Deprovision a worker via SCIM (Enterprise)
- Ensure SAML SSO and SCIM are configured in Deel Settings → Integrations → SSO & SCIM.
- In the IdP (e.g., Okta), deactivate or unassign the user from the Deel SCIM application.
- The IdP sends a PATCH /Users/{id} with active=false to Deel's SCIM endpoint.
- Verify in Deel dashboard that the worker's account is deactivated.
- Separately initiate contract termination via POST /rest/v2/contracts/{contract_id}/terminations if required, as SCIM deactivation does not auto-terminate contracts.
Watch out for: SCIM deactivation only disables login access. Payroll and contract obligations remain active until explicitly terminated through the Deel contract termination flow.
Why building this yourself is a trap
Several non-obvious behaviors will cause silent failures in automated pipelines. API token scopes are immutable after creation; changing required scopes means generating a new token entirely.
SCIM bearer tokens are issued from the SSO/SCIM settings section and are separate from general API tokens-using the wrong token against the SCIM endpoint will return auth errors with no clear indication of the cause.
Pagination is offset-based with no cursor support; concurrent writes during a full sync can produce duplicate or missing records across pages, so large-org syncs should be scheduled during low-write windows or reconciled against webhook events.
Webhook HMAC secrets (used to verify SHA-256 signatures on incoming payloads) are returned only at subscription creation and are not retrievable afterward; losing the secret requires deleting and recreating the subscription. Rate limit thresholds are not publicly documented-implement exponential backoff on HTTP 429 and inspect X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After response headers to adapt dynamically.
Finally, the v1 base URL (https://api.letsdeel.com/v1) is deprecated in favor of v2 (https://api.letsdeel.com/rest/v2); any integration built against v1 endpoints should be migrated before they are removed.
Automate Deel 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.