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

# Submit a payroll migration import for review

POST https://api.letsdeel.com/rest/gp/legal-entities/{legal_entity_id}/payroll-migration-imports/{import_id}/submit

Submit a payroll migration import for review, handing its files to the Deel Payroll Implementation Manager working on the legal entity. An import already submitted is refused rather than submitted twice - treat that as success. Attaching more files reopens an import, so it can be submitted again once it has changed.

 **Token scopes**: `global-payroll:write`

Reference: https://developer.deel.com/api/reference/endpoints/global-payroll/submit-payroll-migration-import-v-2026-09-14

## Authentication

- `Authorization` header (bearer token, required) — ## Authentication The Deel API uses bearer tokens to authenticate requests. All API calls must be made over HTTPS — calls over plain HTTP or without authentication will fail. ```curl curl -X GET 'https://api.letsdeel.com/rest/v2/contracts' \ -H 'Authorization: Bearer YOUR-TOKEN-HERE' ``` [Learn more about authentication](/api/authentication)
- `Authorization` header (bearer token, required) — Standard OAuth2 security scheme based on https://swagger.io/docs/specification/authentication/

## Servers

- `https://api.letsdeel.com/rest` (Production, default)
- `https://api-staging.letsdeel.com/rest` (Demo)

## Request

### Path parameters

- `legal_entity_id` (string, required) — The unique identifier of the legal entity the payroll migration import belongs to.
- `import_id` (string, required) — The unique identifier of the payroll migration import to submit, as returned when its files were attached.

## Response

### 200

OK

- `data` (GpLegalEntitiesLegalEntityIdPayrollMigrationImportsImportIdSubmitPostResponsesContentApplicationJsonSchemaData, required) — The payroll migration import.

## Errors

### 400 Bad Request Error

Operation failed.

- `request` (ApiErrorRequest, optional)
- `errors` (list of ApiError, optional)

### 401 Unauthorized Error

401

- `code` (string, required)
- `status` (double, required)
- `defined` (boolean, required)
- `message` (string, required, default: The request carries no caller identity the gateway could resolve.)
- `data` (any, optional)

### 403 Forbidden Error

403

- `code` (string, required)
- `status` (double, required)
- `defined` (boolean, required)
- `message` (string, required, default: The caller is not authorized to act on this legal entity.)
- `data` (any, optional)

### 404 Not Found Error

404

- `code` (string, required)
- `status` (double, required)
- `defined` (boolean, required)
- `message` (string, required, default: Payroll migration import not found.)
- `data` (any, optional)

### 409 Conflict Error

409

- `code` (string, required)
- `status` (double, required)
- `defined` (boolean, required)
- `message` (string, required, default: This payroll migration import has already been submitted.)
- `data` (any, optional)

### 500 Internal Server Error

500

- `code` (string, required)
- `status` (double, required)
- `defined` (boolean, required)
- `message` (string, required, default: Internal server error.)
- `data` (any, optional)

## Types

### GpLegalEntitiesLegalEntityIdPayrollMigrationImportsImportIdSubmitPostResponsesContentApplicationJsonSchemaData

The payroll migration import.

- `type` (enum, required) — What kind of ingestion this is. Year-to-date payroll history is the only kind today.
  - Allowed values: `YTD_HISTORICAL_INGESTION`
- `files` (list of GpLegalEntitiesLegalEntityIdPayrollMigrationImportsImportIdSubmitPostResponsesContentApplicationJsonSchemaDataFilesItems, required) — The files attached to the import.
- `status` (enum, required) — The state of the import. IN_PROGRESS holds files that have not been submitted, and is what attaching files leaves behind - including on an import that had already been submitted, which reopens it. SUBMITTED once handed to Deel for review. DRAFT is an import started with no files yet, which only happens in the Deel web app.
  - Allowed values: `DRAFT`, `IN_PROGRESS`, `SUBMITTED`
- `import_id` (string, required) — The unique identifier of the payroll migration import.
- `created_at` (string, required) — When the import was created.
- `updated_at` (string, required) — When the import last changed.
- `submitted_at` (string, required, nullable) — When the import was submitted for review, or null when it is not SUBMITTED.
- `legal_entity_id` (string, required) — The legal entity the import belongs to.

### ApiErrorRequest

- `method` (string, optional) — The HTTP method of the failed request
- `url` (string, optional) — The relative URL of the failed request
- `status` (double, optional) — The status code of the response
- `api_req_id` (string, optional) — The request ID of the failed request
- `docs` (string, optional) — A link to the official documentation for the requested endpoint resource
- `source` (string, optional) — The source handler which produced the returned error
- `code` (double, optional) — The code of the source handler which produced the returned error

### ApiError

- `message` (string, optional) — A description of the returned error
- `path` (string, optional) — The JSON path where input validation failed

### GpLegalEntitiesLegalEntityIdPayrollMigrationImportsImportIdSubmitPostResponsesContentApplicationJsonSchemaDataFilesItems

- `source` (enum, required) — Which surface the file arrived through: EXTERNAL for this API, INTERNAL for the Deel web app. An import can hold both when a client and a partner have each added files to it.
  - Allowed values: `INTERNAL`, `EXTERNAL`
- `file_id` (string, required) — The identifier of the file in the Deel file service.
- `file_name` (string, required, nullable) — The name displayed for the file in Deel, when one is known.
- `file_size` (integer, required, nullable) — The size of the file in bytes, when one is known.
- `mime_type` (string, required, nullable) — The media type of the file, when one is known.
- `uploaded_at` (string, required) — When the file was uploaded to the Deel file service.

## Examples

**Response**

```json
{
  "data": {
    "type": "YTD_HISTORICAL_INGESTION",
    "files": [
      {
        "source": "EXTERNAL",
        "file_id": "V1StGXR8_Z5jdHi6B-myT",
        "file_name": "Revolut IE (YTD) 2026.csv",
        "file_size": 524288,
        "mime_type": "text/csv",
        "uploaded_at": "2026-01-15T09:30:00.000Z"
      }
    ],
    "status": "SUBMITTED",
    "import_id": "2f8c9a41-6b3e-4d0a-9f21-77c1e0a5b3d8",
    "created_at": "2026-01-15T09:30:00.000Z",
    "updated_at": "2026-01-15T09:30:00.000Z",
    "submitted_at": "2026-01-15T09:30:00.000Z",
    "legal_entity_id": "9a1f0e77-3c22-4b8d-8e64-2b0f5d9c1a44"
  }
}
```

**SDK Code**

```python
import requests

url = "https://api.letsdeel.com/rest/gp/legal-entities/9a1f0e77-3c22-4b8d-8e64-2b0f5d9c1a44/payroll-migration-imports/2f8c9a41-6b3e-4d0a-9f21-77c1e0a5b3d8/submit"

headers = {"Authorization": "Bearer <token>"}

response = requests.post(url, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.letsdeel.com/rest/gp/legal-entities/9a1f0e77-3c22-4b8d-8e64-2b0f5d9c1a44/payroll-migration-imports/2f8c9a41-6b3e-4d0a-9f21-77c1e0a5b3d8/submit';
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.letsdeel.com/rest/gp/legal-entities/9a1f0e77-3c22-4b8d-8e64-2b0f5d9c1a44/payroll-migration-imports/2f8c9a41-6b3e-4d0a-9f21-77c1e0a5b3d8/submit"

	req, _ := http.NewRequest("POST", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.letsdeel.com/rest/gp/legal-entities/9a1f0e77-3c22-4b8d-8e64-2b0f5d9c1a44/payroll-migration-imports/2f8c9a41-6b3e-4d0a-9f21-77c1e0a5b3d8/submit")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.letsdeel.com/rest/gp/legal-entities/9a1f0e77-3c22-4b8d-8e64-2b0f5d9c1a44/payroll-migration-imports/2f8c9a41-6b3e-4d0a-9f21-77c1e0a5b3d8/submit")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.letsdeel.com/rest/gp/legal-entities/9a1f0e77-3c22-4b8d-8e64-2b0f5d9c1a44/payroll-migration-imports/2f8c9a41-6b3e-4d0a-9f21-77c1e0a5b3d8/submit', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.letsdeel.com/rest/gp/legal-entities/9a1f0e77-3c22-4b8d-8e64-2b0f5d9c1a44/payroll-migration-imports/2f8c9a41-6b3e-4d0a-9f21-77c1e0a5b3d8/submit");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.letsdeel.com/rest/gp/legal-entities/9a1f0e77-3c22-4b8d-8e64-2b0f5d9c1a44/payroll-migration-imports/2f8c9a41-6b3e-4d0a-9f21-77c1e0a5b3d8/submit")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```