curl -X POST "https://api.dcycle.io/v2/employees" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 8a7b6c5d-4e3f-4a21-9b0c-1d2e3f4a5b6c" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"email": "ana.garcia@example.com",
"name": "Ana García",
"situation": "active",
"client_row_id": "HR-1042"
}
]
}'
import os
import uuid
import requests
response = requests.post(
"https://api.dcycle.io/v2/employees",
headers={
"x-api-key": os.environ["DCYCLE_API_KEY"],
"x-organization-id": os.environ["DCYCLE_ORG_ID"],
"Idempotency-Key": str(uuid.uuid4()), # keep it to retry this same batch
},
json={
"records": [
{
"email": "ana.garcia@example.com",
"name": "Ana García",
"situation": "active",
"client_row_id": "HR-1042",
}
]
},
timeout=60,
)
response.raise_for_status()
print([item["employee"]["id"] for item in response.json()["items"]])
{
"items": [
{
"source_row_index": 0,
"client_row_id": "HR-1042",
"employee": {
"id": "4c3b2a19-0f8e-4d7c-9b6a-5f4e3d2c1b0a",
"organization_id": "a8315ef3-dd50-43f8-b7ce-d839e68d51fa",
"email": "ana.garcia@example.com",
"name": "Ana García",
"situation": "active",
"status": "loading",
"created_at": "2026-10-20T09:12:44.031Z"
}
}
]
}
{
"type": "https://code.dcycle.io/api-reference/errors#BULK_RECORDS_REJECTED",
"title": "Unprocessable Entity",
"status": 422,
"code": "BULK_RECORDS_REJECTED",
"detail": "1 of the submitted records are invalid; nothing was written.",
"errors": [
{
"source_row_index": 0,
"client_row_id": "HR-1042",
"error_code": "EMPLOYEE_ALREADY_EXISTS",
"error_params": {
"field": "email",
"value": "ana.garcia@example.com",
"employee_id": "4c3b2a19-0f8e-4d7c-9b6a-5f4e3d2c1b0a"
}
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 1, "valid": 1 }
Create Employees (v2)
Create up to 1,000 employees of the commuting survey in one request
POST
/
v2
/
employees
curl -X POST "https://api.dcycle.io/v2/employees" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 8a7b6c5d-4e3f-4a21-9b0c-1d2e3f4a5b6c" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"email": "ana.garcia@example.com",
"name": "Ana García",
"situation": "active",
"client_row_id": "HR-1042"
}
]
}'
import os
import uuid
import requests
response = requests.post(
"https://api.dcycle.io/v2/employees",
headers={
"x-api-key": os.environ["DCYCLE_API_KEY"],
"x-organization-id": os.environ["DCYCLE_ORG_ID"],
"Idempotency-Key": str(uuid.uuid4()), # keep it to retry this same batch
},
json={
"records": [
{
"email": "ana.garcia@example.com",
"name": "Ana García",
"situation": "active",
"client_row_id": "HR-1042",
}
]
},
timeout=60,
)
response.raise_for_status()
print([item["employee"]["id"] for item in response.json()["items"]])
{
"items": [
{
"source_row_index": 0,
"client_row_id": "HR-1042",
"employee": {
"id": "4c3b2a19-0f8e-4d7c-9b6a-5f4e3d2c1b0a",
"organization_id": "a8315ef3-dd50-43f8-b7ce-d839e68d51fa",
"email": "ana.garcia@example.com",
"name": "Ana García",
"situation": "active",
"status": "loading",
"created_at": "2026-10-20T09:12:44.031Z"
}
}
]
}
{
"type": "https://code.dcycle.io/api-reference/errors#BULK_RECORDS_REJECTED",
"title": "Unprocessable Entity",
"status": 422,
"code": "BULK_RECORDS_REJECTED",
"detail": "1 of the submitted records are invalid; nothing was written.",
"errors": [
{
"source_row_index": 0,
"client_row_id": "HR-1042",
"error_code": "EMPLOYEE_ALREADY_EXISTS",
"error_params": {
"field": "email",
"value": "ana.garcia@example.com",
"employee_id": "4c3b2a19-0f8e-4d7c-9b6a-5f4e3d2c1b0a"
}
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 1, "valid": 1 }
← Employees API
Add employees to the directory of the commuting survey: a name, an email or both, and whether they still work there.
Nothing is calculated, so the request is synchronous (see
how bulk ingestion works): it answers
Request-level errors:
201 Created with the created employees.
It also records an ingest job, born completed, that sends no webhook.
An employee has no emissions until their commuting periods are known: send them with
Create Commuting Periods (v2), or collect them with the commuting
survey. This route never sends an email: no survey goes out when an employee is created.
Beta. Part of the bulk ingest API, in beta. The contract may still change before general availability.
curl -X POST "https://api.dcycle.io/v2/employees" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Idempotency-Key: 8a7b6c5d-4e3f-4a21-9b0c-1d2e3f4a5b6c" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"email": "ana.garcia@example.com",
"name": "Ana García",
"situation": "active",
"client_row_id": "HR-1042"
}
]
}'
import os
import uuid
import requests
response = requests.post(
"https://api.dcycle.io/v2/employees",
headers={
"x-api-key": os.environ["DCYCLE_API_KEY"],
"x-organization-id": os.environ["DCYCLE_ORG_ID"],
"Idempotency-Key": str(uuid.uuid4()), # keep it to retry this same batch
},
json={
"records": [
{
"email": "ana.garcia@example.com",
"name": "Ana García",
"situation": "active",
"client_row_id": "HR-1042",
}
]
},
timeout=60,
)
response.raise_for_status()
print([item["employee"]["id"] for item in response.json()["items"]])
{
"items": [
{
"source_row_index": 0,
"client_row_id": "HR-1042",
"employee": {
"id": "4c3b2a19-0f8e-4d7c-9b6a-5f4e3d2c1b0a",
"organization_id": "a8315ef3-dd50-43f8-b7ce-d839e68d51fa",
"email": "ana.garcia@example.com",
"name": "Ana García",
"situation": "active",
"status": "loading",
"created_at": "2026-10-20T09:12:44.031Z"
}
}
]
}
{
"type": "https://code.dcycle.io/api-reference/errors#BULK_RECORDS_REJECTED",
"title": "Unprocessable Entity",
"status": 422,
"code": "BULK_RECORDS_REJECTED",
"detail": "1 of the submitted records are invalid; nothing was written.",
"errors": [
{
"source_row_index": 0,
"client_row_id": "HR-1042",
"error_code": "EMPLOYEE_ALREADY_EXISTS",
"error_params": {
"field": "email",
"value": "ana.garcia@example.com",
"employee_id": "4c3b2a19-0f8e-4d7c-9b6a-5f4e3d2c1b0a"
}
}
],
"rejected": 1,
"request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
}
{ "dry_run": true, "submitted": 1, "valid": 1 }
How it works
- Every record is checked: it has an email, a name or both, and its email is free. An email is unique in the
organization, without regard to case: one that an employee of the organization already has, or that another record
of the request has, rejects the record. If any record is invalid the whole request is rejected with
422and nothing is created (all or nothing):rejectedis the number of invalid records anderrorslists them (up to 100). - Every employee is created in one transaction and returned, in the order sent, with
status: loading(as the app’s import does) until their commute is known.source_row_indexis the position of the record inrecords, andclient_row_idechoes yours. - The employees belong to the
x-organization-idorganization. A record cannot name another one, not even one of your perimeter: send each organization’s employees with its ownx-organization-id.
Idempotency-Key.
Request
Headers
string
required
Your API key (see Authentication).
string
required
The organization the employees belong to.
string
required
1 to 255 characters, one per batch, looked up per organization across every v2 bulk route. Within 24 hours, the
same key with the same route and body answers the same employees (with
Idempotent-Replayed: true) instead of
creating them again; the same key with another body or on another route is 422 IDEMPOTENCY_KEY_REUSED, and while
the first request is still being processed 409 IDEMPOTENCY_KEY_IN_PROGRESS. After 24 hours the key is free, and a
request with it is a new one. See The Idempotency-Key.Query Parameters
boolean
default:"false"
Check only: the same
422 for invalid records, nothing written; 200 with
{"dry_run": true, "submitted": n, "valid": n} when every record is valid. It does not check or store the
Idempotency-Key.Body Parameters
array[object]
required
The employees to create, 1 to 1,000. A field not listed here is rejected (
SCHEMA_INVALID).Show record
Show record
string
Their email, up to 255 characters, unique in the organization (case does not matter). Required if
name is not
sent.string
Their name, 1 to 255 characters. Required if
email is not sent.string
default:"active"
active, inactive (on leave) or terminated (no longer works there).string
Your own reference for the record, up to 255 characters, echoed in errors and in the answer.
Response
201 Created with items: one per record, in the order sent, with its source_row_index, client_row_id and the
created employee (id, organization_id, email, name, situation, status, created_at). Its id is the one
Delete Employees (v2) takes.
Errors
Problem details. Per-record codes inerrors[].error_code of a 422 BULK_RECORDS_REJECTED:
error_code | Cause |
|---|---|
SCHEMA_INVALID | The record does not match the schema: neither email nor name, an email that is not valid, an empty name, a situation not listed, an unknown field (e.g. organization_id or status)… error_params.errors says where. |
EMPLOYEE_ALREADY_EXISTS | An employee of the organization already has this email (case aside). error_params.employee_id is theirs. |
DUPLICATE_EMAIL | Another record of the request has the same email (case aside). |
401 for a missing or invalid API key, 403 LOGGED_USER_NOT_MEMBER when the key’s user is not a
member of the organization, 422 REQUEST_VALIDATION_FAILED for a malformed body (no records, more than 1,000, no
Idempotency-Key), 422 IDEMPOTENCY_KEY_REUSED, 409 IDEMPOTENCY_KEY_IN_PROGRESS, and 429 beyond the
rate limits.
Limits
| Limit | Value |
|---|---|
| Records per request | 1 to 1,000 |
Related Endpoints
Delete Employees (v2)
Delete employees by id
List Employees
The employees stored, and their ids
Resend Commuting Survey
Send the survey, on request
List Ingest Jobs
The job each request records
Was this page helpful?