> This page is for API, version 2026-01-01 (Stable) (default).
> For other versions, use one of these documentation indexes:
> - 2026-01-01 (Stable) (default): https://developer.deel.com/api/stable/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.deel.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.deel.com/_mcp/server.

# From candidate to contract

## Overview

When a candidate accepts an offer in Deel's ATS, the hiring record is complete but no contract exists yet. This guide walks through the steps to retrieve the accepted offer from the ATS, determine the correct contract type based on the offer's `worker_type`, and initiate the contract creation request using Deel's contracts API.

The process is partly manual today: offer retrieval and contract creation are separate API surfaces, and the link between them requires passing key fields from the offer (such as candidate email, employment type, and location) into the contract request.

## When to use this workflow

Use this workflow when you need to:

* Automatically trigger contract creation as soon as a candidate accepts an offer
* Sync hire decisions from Deel's ATS into a downstream HRIS or payroll system
* Build an end-to-end recruitment-to-onboarding automation without manual handoffs
* Audit which ATS applications have progressed to active contracts

## Prerequisites

Before starting, ensure you have:

* A valid API token with `ats:read` scope to retrieve offer data
* Contract creation permissions: `contracts:write` scope and a Deel account configured for the relevant contract type
* A completed ATS application that has reached a `COMPLETED` stage with an accepted offer. See [Manage candidates and applications](/api/ats-guides/manage-candidates-and-applications).
* The `application_id` of the hired candidate

## Step-by-step workflow

The following example follows Jane Doe, whose application `app_01hxyzghijkl` has progressed to a `COMPLETED` stage with an accepted offer.

### Retrieve the application and check offer status

Fetch the full application record to confirm an offer exists and has been accepted.

#### Request

```bash
curl --request GET 'https://api.letsdeel.com/rest/ats/applications/app_01hxyzghijkl' \
  --header 'Authorization: Bearer YOUR-TOKEN-HERE'
```

Response (abbreviated):

```json focus={5,8,9,10}
{
  "data": {
    "id": "app_01hxyzghijkl",
    "candidate": {
      "email": "jane.doe@example.com"
    },
    "current_application_interview_plan_stage": {
      "interview_plan_stage": {
        "category_type_slug": "COMPLETED"
      }
    },
    "offers": [
      {
        "id": "offer_01hxyzabcdef",
        "offer_status": "ACCEPTED",
        "worker_type": "EOR_EMPLOYEE",
        "created_at": "2026-05-04T11:00:00Z"
      }
    ]
  }
}
```

Confirm two conditions before proceeding:

* `current_application_interview_plan_stage.interview_plan_stage.category_type_slug` is `"COMPLETED"`
* `offers[0].offer_status` is `"ACCEPTED"`

If no offer exists or the offer status is not `"ACCEPTED"`, the application is not ready for contract creation.

### Retrieve offer details

Fetch the full offer to get the fields required for contract creation.

#### Request

```bash
curl --request GET 'https://api.letsdeel.com/rest/ats/offers/offer_01hxyzabcdef' \
  --header 'Authorization: Bearer YOUR-TOKEN-HERE'
```

Response (abbreviated):

```json focus={4,5,6,7}
{
  "data": {
    "id": "offer_01hxyzabcdef",
    "worker_type": "EOR_EMPLOYEE",
    "offer_status": "ACCEPTED",
    "salary": { "amount": 95000, "currency": "USD", "period": "YEAR" },
    "start_date": "2026-06-01",
    "country_code": "ES",
    "job_title": "Senior Software Engineer"
  }
}
```

Store the following fields. You will need them in the next step:

* `worker_type`: determines which contract endpoint to call
* `salary`, `start_date`, `country_code`, `job_title`
* The candidate's email from the application record

> **Tip**
>
> If `offers` is empty on the application record, the job's interview plan may not have an offer stage configured. Set up offer stages in the Deel application under the interview plan settings.

### Determine the contract type

The `worker_type` on the offer determines which Deel contract API to use.

| `worker_type`             | Contract type                     | Endpoint                 |
| ------------------------- | --------------------------------- | ------------------------ |
| `EOR_EMPLOYEE`            | Employer of Record employee       | `POST /eor`              |
| `CONTRACTOR`              | Independent contractor            | `POST /contracts/ic`     |
| `DIRECT_EMPLOYEE`         | Direct employee (your own entity) | `POST /contracts/direct` |
| `GLOBAL_PAYROLL_EMPLOYEE` | Global payroll employee           | `POST /contracts/gp`     |

The candidate's offer has `worker_type: "EOR_EMPLOYEE"`, so you will create an EOR contract.

### Create the contract

Pass the offer details into the contract creation request. EOR contracts use a different, more detailed schema than the other contract types: `POST /eor` requires nested `employee`, `employment`, `client`, and `compensation_details` objects, plus a `seniority` reference. Refer to the [Create EOR contract reference](/api/reference/endpoints/eor-contract/create-eor-contract) for the full schema.

> **Note**
>
> The offer and application records give you `job_title`, `country_code`, `start_date`, and `salary`. They do not include the candidate's `nationality`, a `seniority` ID, or a `client.team` ID, so look these up separately before creating the contract:
>
> * `client.team.id`: the group this contract belongs to. Retrieve with [`GET /teams`](/api/reference/endpoints/organizations/get-teams-v-2026-01-01).
> * `seniority.id`: match the offer's job level to a value from [`GET /lookups/seniorities`](/api/reference/endpoints/lookups/get-seniority-levels).
> * `employee.nationality`: not captured by the ATS. Collect it separately (for example, during the candidate's own onboarding form) before submitting the contract.

#### Request

```bash
curl --request POST 'https://api.letsdeel.com/rest/eor' \
  --header 'Authorization: Bearer YOUR-TOKEN-HERE' \
  --header 'Content-Type: application/json' \
  --data '{
    "data": {
      "employee": {
        "first_name": "Jane",
        "last_name": "Doe",
        "email": "jane.doe@example.com",
        "nationality": "ES"
      },
      "job_title": "Senior Software Engineer",
      "seniority": {
        "id": "seniority-123"
      },
      "employment": {
        "country": "ES",
        "start_date": "2026-06-01",
        "work_visa_required": false,
        "scope_of_work": "Design, build, and maintain backend services for the engineering team, collaborating with product and design on new features."
      },
      "client": {
        "team": {
          "id": "team-12345"
        }
      },
      "compensation_details": {
        "salary": 95000,
        "currency": "USD"
      }
    }
  }'
```

A successful response returns the new contract ID. Store it to link the contract back to the ATS application in your own system.

> **Warning**
>
> Contract creation does not automatically update the ATS application. There is no API to attach a contract ID to an application record. Track the link between `application_id` and `contract_id` in your own system.

### Verify the contract was created

Retrieve the contract to confirm it is in `CREATED` status and the worker details match.

#### Request

```bash
curl --request GET 'https://api.letsdeel.com/rest/contracts/{contract_id}' \
  --header 'Authorization: Bearer YOUR-TOKEN-HERE'
```

The contract moves through its own lifecycle (`CREATED` → `PENDING_SIGNATURE` → `ACTIVE`) independently of the ATS application.

## Common scenarios

### Contractor instead of EOR employee

If `worker_type` is `CONTRACTOR`, use `POST /contracts/ic` instead. The key difference is that IC contracts require the worker to register as a contractor entity. Ensure the worker has completed their Deel contractor profile before creating the contract.

### Offer not yet accepted

If `offer_status` is `SENT` (not yet `ACCEPTED`), the candidate has not yet accepted. Poll [`GET /ats/offers/{offer_id}`](/api/ats-endpoints/ats-offers/retrieve-a-list-of-ats-offers-v-2026-01-01) or set up an `ats.application.transitioned` webhook to receive a notification when the application stage changes to `COMPLETED`. Do not create a contract for an unaccepted offer.

### Multiple offers on one application

An application can have more than one offer (for example, if a first offer was rejected and a new one was extended). Always check the full `offers` array and use the offer with `offer_status: "ACCEPTED"`. If multiple offers exist, use the one with the most recent `created_at` timestamp.

## Troubleshooting

#### offers array is empty on the application

Deel generates an offer only when the interview plan includes an offer stage and a hiring manager creates one in the Deel application. If the `offers` array is empty, the application has not reached the offer stage yet, or a hiring manager has not created the offer. Check the application's `current_application_interview_plan_stage` and confirm the interview plan includes an offer stage.

#### offer_status is SENT but never moves to ACCEPTED

The candidate must accept the offer through their Deel candidate portal. If the candidate has not received the email, verify their email address on the candidate record with [`GET /ats/candidates/{candidate_id}`](/api/ats-endpoints/ats-candidates/retrieve-paginated-list-of-ats-candidates-v-2026-01-01). Resend the offer email in the Deel application.

#### Contract creation returns 422: worker email not found

The worker email must match a Deel user account. If the candidate does not yet have a Deel account, contract creation will prompt them to register. Ensure the email on the offer matches the email you are sending to the contracts endpoint.

#### worker_type on the offer does not match expectations

Deel sets the `worker_type` when a hiring manager creates the offer, based on the employment type configured for the job. If the value is unexpected, verify the `job_employment_type` on the application and the offer configuration in the Deel application.

#### Cannot find the correct contract endpoint for the worker_type

Use the `worker_type` value from the offer to select the correct endpoint: `EOR_EMPLOYEE` → `/eor`, `CONTRACTOR` → `/contracts/ic`, `DIRECT_EMPLOYEE` → `/contracts/direct`, `GLOBAL_PAYROLL_EMPLOYEE` → `/contracts/gp`. Full schema for the EOR endpoint is in the [Create EOR contract reference](/api/reference/endpoints/eor-contract/create-eor-contract); the others are in the [Contracts API reference](/api/reference/endpoints/contracts).

## Next steps

#### [Contracts API reference](/api/reference/endpoints/contracts)

Full schema and required fields for EOR, IC, direct, and global payroll contracts.

#### [Candidates and applications](/api/ats-guides/manage-candidates-and-applications)

Review the full application workflow before the hire decision.

#### [ATS webhooks](/api/ats-guides/webhooks)

Receive real-time notifications when an application reaches a completed stage.

#### [ATS overview](/api/ats-guides/introduction)

Review the full ATS data model and API capabilities.