From candidate to contract

Retrieve an accepted offer and initiate a Deel contract for a hired candidate

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.
  • 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.

1

Retrieve the application and check offer status

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

Request
$curl --request GET 'https://api.letsdeel.com/rest/v2/ats/applications/app_01hxyzghijkl' \
> --header 'Authorization: Bearer YOUR-TOKEN-HERE'

Response (abbreviated):

1{
2 "data": {
3 "id": "app_01hxyzghijkl",
4 "candidate": {
5 "email": "jane.doe@example.com"
6 },
7 "current_application_interview_plan_stage": {
8 "interview_plan_stage": {
9 "category_type_slug": "COMPLETED"
10 }
11 },
12 "offers": [
13 {
14 "id": "offer_01hxyzabcdef",
15 "offer_status": "ACCEPTED",
16 "worker_type": "EOR_EMPLOYEE",
17 "created_at": "2026-05-04T11:00:00Z"
18 }
19 ]
20 }
21}

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.

2

Retrieve offer details

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

Request
$curl --request GET 'https://api.letsdeel.com/rest/v2/ats/offers/offer_01hxyzabcdef' \
> --header 'Authorization: Bearer YOUR-TOKEN-HERE'

Response (abbreviated):

1{
2 "data": {
3 "id": "offer_01hxyzabcdef",
4 "worker_type": "EOR_EMPLOYEE",
5 "offer_status": "ACCEPTED",
6 "salary": { "amount": 95000, "currency": "USD", "period": "YEAR" },
7 "start_date": "2026-06-01",
8 "country_code": "ES",
9 "job_title": "Senior Software Engineer"
10 }
11}

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

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.

3

Determine the contract type

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

worker_typeContract typeEndpoint
EOR_EMPLOYEEEmployer of Record employeePOST /contracts/eor
CONTRACTORIndependent contractorPOST /contracts/ic
DIRECT_EMPLOYEEDirect employee (your own entity)POST /contracts/direct
GLOBAL_PAYROLL_EMPLOYEEGlobal payroll employeePOST /contracts/gp

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

4

Create the contract

Pass the offer details into the contract creation request. The exact fields required vary by contract type. Refer to the Contracts API reference for the full schema.

Request
$curl --request POST 'https://api.letsdeel.com/rest/v2/contracts/eor' \
> --header 'Authorization: Bearer YOUR-TOKEN-HERE' \
> --header 'Content-Type: application/json' \
> --data '{
> "data": {
> "worker_email": "jane.doe@example.com",
> "job_title": "Senior Software Engineer",
> "country_code": "ES",
> "start_date": "2026-06-01",
> "salary": {
> "amount": 95000,
> "currency": "USD",
> "period": "YEAR"
> }
> }
> }'

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

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.

5

Verify the contract was created

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

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

The contract moves through its own lifecycle (CREATEDPENDING_SIGNATUREACTIVE) 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} 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

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.

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}. Resend the offer email in the Deel application.

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.

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.

Use the worker_type value from the offer to select the correct endpoint: EOR_EMPLOYEE/contracts/eor, CONTRACTOR/contracts/ic, DIRECT_EMPLOYEE/contracts/direct, GLOBAL_PAYROLL_EMPLOYEE/contracts/gp. Full schema for each endpoint is in the Contracts API reference.

Next steps