Upload Workforce Employees CSV
const options = {
method: 'POST',
headers: {
'x-api-key': '<x-api-key>',
'x-organization-id': '<x-organization-id>',
'x-partner': '<x-partner>',
'Content-Type': 'application/json'
},
body: JSON.stringify({file_name: '<string>'})
};
fetch('https://api.dcycle.io/v1/own_workforces/bulk/csv', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.dcycle.io/v1/own_workforces/bulk/csv"
payload = { "file_name": "<string>" }
headers = {
"x-api-key": "<x-api-key>",
"x-organization-id": "<x-organization-id>",
"x-partner": "<x-partner>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)curl --request POST \
--url https://api.dcycle.io/v1/own_workforces/bulk/csv \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--header 'x-organization-id: <x-organization-id>' \
--header 'x-partner: <x-partner>' \
--data '
{
"file_name": "<string>"
}
'{
"upload_url": "<string>",
"file_id": "<string>",
"destination_file_key": "<string>",
"file_name": "<string>",
"organization_id": "<string>",
"message": "<string>"
}Upload Workforce Employees CSV
Request a presigned S3 URL to upload an employee CSV for asynchronous ingestion
POST
/
v1
/
own_workforces
/
bulk
/
csv
Upload Workforce Employees CSV
const options = {
method: 'POST',
headers: {
'x-api-key': '<x-api-key>',
'x-organization-id': '<x-organization-id>',
'x-partner': '<x-partner>',
'Content-Type': 'application/json'
},
body: JSON.stringify({file_name: '<string>'})
};
fetch('https://api.dcycle.io/v1/own_workforces/bulk/csv', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.dcycle.io/v1/own_workforces/bulk/csv"
payload = { "file_name": "<string>" }
headers = {
"x-api-key": "<x-api-key>",
"x-organization-id": "<x-organization-id>",
"x-partner": "<x-partner>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)curl --request POST \
--url https://api.dcycle.io/v1/own_workforces/bulk/csv \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--header 'x-organization-id: <x-organization-id>' \
--header 'x-partner: <x-partner>' \
--data '
{
"file_name": "<string>"
}
'{
"upload_url": "<string>",
"file_id": "<string>",
"destination_file_key": "<string>",
"file_name": "<string>",
"organization_id": "<string>",
"message": "<string>"
}← Own Workforce API
Mint a presigned S3 URL and upload an employee CSV to it. The call itself ingests nothing: it hands you a URL, and the file is parsed asynchronously once it lands in the bucket.
The presigned URL is short-lived. Request it immediately before uploading rather than storing it.
Prefer the Imports API for new integrations. It covers the same three workforce templates with column mapping, row-level validation before submission, and per-row error reporting — see Own Workforce Import Templates. This endpoint is the older path: it has no validation step and no way to correct rows, so a malformed file fails silently after the upload rather than at request time. It is documented because it is open and in use.
How it works
1
Ask for a URL
POST here with the file name you intend to upload. You get back upload_url, a file_id and the key the object will live under.2
PUT the file
Upload the CSV bytes to
upload_url with an HTTP PUT. This request goes to S3, not to the Dcycle API, and carries no Dcycle headers.3
Wait for ingestion
Arrival triggers the ingestion job. Nothing is returned synchronously — poll List Workforce Employees filtering on the
file_id you were given, and read each row’s status.Request
Headers
string
required
Your API key for authenticationExample:
sk_live_1234567890abcdefstring
required
Your organization UUIDExample:
a8315ef3-dd50-43f8-b7ce-d839e68d51fastring
required
The partner the upload is filed under. Required only on this endpoint — the trainings and absences uploads read it from the organization instead, so it is easy to miss when copying one of those.Example:
dcycleBody
string
required
Name of the file you are about to upload. It is stored alongside the rows and surfaces as
file_name on the employee list.Example: workforce_2026.csvResponse
201 Created.
string
Presigned S3 URL.
PUT the file bytes here.string
Id of the file record. Use it as
file_id[] on the employee list to follow the ingestion, and later to delete everything that came from this upload.string
Object key the file will be stored under
string
Echo of the name you sent
string
Organization the upload is attributed to
string
Human-readable confirmation
Example
# 1. Ask for the URL
curl -X POST "https://api.dcycle.io/v1/own_workforces/bulk/csv" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "x-partner: ${DCYCLE_PARTNER}" \
-H "Content-Type: application/json" \
-d '{"file_name": "workforce_2026.csv"}'
# 2. Upload the file to the returned upload_url (no Dcycle headers here)
curl -X PUT "${UPLOAD_URL}" --upload-file workforce_2026.csv
import os
import requests
headers = {
"x-api-key": os.getenv("DCYCLE_API_KEY"),
"x-organization-id": os.getenv("DCYCLE_ORG_ID"),
"x-partner": os.getenv("DCYCLE_PARTNER", "dcycle"),
}
presigned = requests.post(
"https://api.dcycle.io/v1/own_workforces/bulk/csv",
headers=headers,
json={"file_name": "workforce_2026.csv"},
).json()
with open("workforce_2026.csv", "rb") as handle:
upload = requests.put(presigned["upload_url"], data=handle)
upload.raise_for_status()
print("ingesting under file_id", presigned["file_id"])
const axios = require('axios');
const fs = require('fs');
const headers = {
'x-api-key': process.env.DCYCLE_API_KEY,
'x-organization-id': process.env.DCYCLE_ORG_ID,
'x-partner': process.env.DCYCLE_PARTNER || 'dcycle'
};
axios.post('https://api.dcycle.io/v1/own_workforces/bulk/csv',
{ file_name: 'workforce_2026.csv' }, { headers })
.then(({ data }) =>
axios.put(data.upload_url, fs.createReadStream('workforce_2026.csv'))
.then(() => console.log('ingesting under file_id', data.file_id))
)
.catch(error => console.error(error));
Successful Response
{
"upload_url": "https://dcycle-uploads.s3.eu-west-1.amazonaws.com/...",
"file_id": "9f1c7f2a-64a1-4b2c-9d3e-70a5b8c1d2e3",
"destination_file_key": "dcycle/a8315ef3-dd50-43f8-b7ce-d839e68d51fa/own_workforce/workforce_2026.csv",
"file_name": "workforce_2026.csv",
"organization_id": "a8315ef3-dd50-43f8-b7ce-d839e68d51fa",
"message": "Presigned URL created successfully"
}
Common Errors
401 Unauthorized
Cause: the key is invalid, or it does not belong to the organization inx-organization-id — the two are looked up as a pair. A request carrying no credentials at all answers AUTH_REQUIRED instead.
{
"detail": "Invalid API key for organization",
"code": "INVALID_API_KEY"
}
403 Forbidden
Cause: the key’s owner is not an enabled member of the organization, or their role cannot write.{
"detail": "Logged User is not Member of Organization",
"code": "LOGGED_USER_NOT_MEMBER"
}
422 Unprocessable Entity
Cause:file_name missing from the body, or the required x-partner header absent.
{
"detail": [
{
"type": "missing",
"loc": ["body", "file_name"],
"msg": "Field required"
}
]
}
Related Endpoints
Own workforce import templates
The columns this CSV must carry
Create an import session
The recommended write path
Was this page helpful?