> ## Documentation Index
> Fetch the complete documentation index at: https://code.dcycle.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Upload Workforce Employees CSV

> Request a presigned S3 URL to upload an employee CSV for asynchronous ingestion

[← Own Workforce API](/api-reference/own-workforce/overview)

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.

<Note>
  **Prefer the [Imports API](/api-reference/imports/overview) 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](/api-reference/imports/own-workforce-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.
</Note>

## How it works

<Steps>
  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Wait for ingestion">
    Arrival triggers the ingestion job. Nothing is returned synchronously — poll [List Workforce Employees](/api-reference/own-workforce/list) filtering on the `file_id` you were given, and read each row's `status`.
  </Step>
</Steps>

The presigned URL is short-lived. Request it immediately before uploading rather than storing it.

## Request

### Headers

<ParamField header="x-api-key" type="string" required>
  Your API key for authentication

  **Example:** `sk_live_1234567890abcdef`
</ParamField>

<ParamField header="x-organization-id" type="string" required>
  Your organization UUID

  **Example:** `a8315ef3-dd50-43f8-b7ce-d839e68d51fa`
</ParamField>

<ParamField header="x-partner" type="string" 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:** `dcycle`
</ParamField>

### Body

<ParamField body="file_name" type="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.csv`
</ParamField>

## Response

`201 Created`.

<ResponseField name="upload_url" type="string">
  Presigned S3 URL. `PUT` the file bytes here.
</ResponseField>

<ResponseField name="file_id" type="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.
</ResponseField>

<ResponseField name="destination_file_key" type="string">
  Object key the file will be stored under
</ResponseField>

<ResponseField name="file_name" type="string">
  Echo of the name you sent
</ResponseField>

<ResponseField name="organization_id" type="string">
  Organization the upload is attributed to
</ResponseField>

<ResponseField name="message" type="string">
  Human-readable confirmation
</ResponseField>

## Example

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  # 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
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  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"])
  ```

  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  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));
  ```
</CodeGroup>

### Successful Response

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "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 in `x-organization-id` — the two are looked up as a pair. A request carrying no credentials at all answers `AUTH_REQUIRED` instead.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "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.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "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.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "detail": [
    {
      "type": "missing",
      "loc": ["body", "file_name"],
      "msg": "Field required"
    }
  ]
}
```

## Related Endpoints

<CardGroup cols={2}>
  <Card title="Own workforce import templates" icon="table-columns" href="/api-reference/imports/own-workforce-templates">
    The columns this CSV must carry
  </Card>

  <Card title="Create an import session" icon="file-import" href="/api-reference/imports/create-session">
    The recommended write path
  </Card>
</CardGroup>
