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

# Create Workforce Trainings (v2)

> Create up to 1,000 trainings of your employees in one request

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

Record the trainings your employees received: what, when and for how many hours. Each training belongs to an
employee, by the `id` that [Create Workforce Employees (v2)](/api-reference/own-workforce/employees-create-v2)
answers. The request answers `201 Created` with the trainings; nothing is calculated, so there is no job to follow.

<Warning>
  **Beta.** Part of the bulk ingest API, currently in beta. The contract may still change before general availability.
</Warning>

<RequestExample>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST "https://api.dcycle.io/v2/own-workforce/trainings" \
    -H "x-api-key: ${DCYCLE_API_KEY}" \
    -H "x-organization-id: ${DCYCLE_ORG_ID}" \
    -H "Idempotency-Key: 5e4d3c2b-1a09-4f8e-9d7c-6b5a49382716" \
    -H "Content-Type: application/json" \
    -d '{
      "records": [
        {
          "own_workforce_id": "6f5e4d3c-2b1a-4f09-8e7d-6c5b4a392817",
          "description": "Forklift safety",
          "type": "safety",
          "start_date": "2026-03-04",
          "end_date": "2026-03-05",
          "hours": 12.5,
          "client_row_id": "LMS-8841"
        }
      ]
    }'
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import os
  import uuid

  import requests

  response = requests.post(
      "https://api.dcycle.io/v2/own-workforce/trainings",
      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": [
              {
                  "own_workforce_id": "6f5e4d3c-2b1a-4f09-8e7d-6c5b4a392817",
                  "description": "Forklift safety",
                  "type": "safety",
                  "start_date": "2026-03-04",
                  "end_date": "2026-03-05",
                  "hours": 12.5,
              }
          ]
      },
      timeout=60,
  )
  response.raise_for_status()
  print([item["training"]["id"] for item in response.json()["items"]])
  ```
</RequestExample>

<ResponseExample>
  ```json 201 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "items": [
      {
        "source_row_index": 0,
        "client_row_id": "LMS-8841",
        "training": {
          "id": "3b2a1908-7f6e-4d5c-8b4a-392817160504",
          "own_workforce_id": "6f5e4d3c-2b1a-4f09-8e7d-6c5b4a392817",
          "description": "Forklift safety",
          "type": "safety",
          "start_date": "2026-03-04",
          "end_date": "2026-03-05",
          "hours": 12.5,
          "created_at": "2026-10-19T09:12:44.031Z"
        }
      }
    ]
  }
  ```

  ```json 422 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "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": "LMS-8841",
        "error_code": "REFERENCE_NOT_FOUND",
        "error_params": { "field": "own_workforce_id", "value": "6f5e4d3c-2b1a-4f09-8e7d-6c5b4a392817" }
      }
    ],
    "rejected": 1,
    "request_id": "b2248a31-0f1b-4a7d-83ce-b29bd5977b4c"
  }
  ```

  ```json 200 (dry_run=true) theme={"theme":{"light":"github-light","dark":"github-dark"}}
  { "dry_run": true, "submitted": 1, "valid": 1 }
  ```
</ResponseExample>

## How it works

Every record is checked. If any is invalid the **whole** request is rejected with `422` and nothing is written; the
answer lists every invalid record, so you can fix them all at once. Otherwise the trainings are created at once.

A training is not matched against the ones already stored: sending the same training twice creates it twice. To
retry a request safely, repeat it with the same `Idempotency-Key`.

## Request

### Headers

<ParamField header="x-api-key" type="string" required>
  Your API key (see [Authentication](/api-reference/introduction#authentication)).
</ParamField>

<ParamField header="x-organization-id" type="string" required>
  Your organization. Its employees, and those of the organizations under it that you can write to, can take
  trainings; each training belongs to its employee's organization.
</ParamField>

<ParamField header="Idempotency-Key" type="string" required>
  1 to 255 characters, unique per batch. A retry with the same key and body answers the same trainings (with
  `Idempotent-Replayed: true`) instead of creating them again; the same key with another body is
  `422 IDEMPOTENCY_KEY_REUSED`, and while the first request is still being processed `409 IDEMPOTENCY_KEY_IN_PROGRESS`.
</ParamField>

### Query Parameters

<ParamField query="dry_run" type="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.
</ParamField>

### Body Parameters

<ParamField body="records" type="array[object]" required>
  The trainings to create, 1 to 1,000. A field not listed here is rejected (`SCHEMA_INVALID`).

  <Expandable title="record">
    <ParamField body="own_workforce_id" type="uuid" required>
      The employee trained: an `id` of [Create Workforce Employees (v2)](/api-reference/own-workforce/employees-create-v2)
      or [List Workforce Employees](/api-reference/own-workforce/list).
    </ParamField>

    <ParamField body="description" type="string" required>
      What the training was, up to 255 characters.
    </ParamField>

    <ParamField body="type" type="string" required>
      Your category for the training, as free text, up to 255 characters (`safety`, `technical`, `languages`...).
    </ParamField>

    <ParamField body="start_date" type="date" required>
      First day of the training, `YYYY-MM-DD`, from 1970 to next year.
    </ParamField>

    <ParamField body="end_date" type="date" required>
      Last day of the training, on or after `start_date`.
    </ParamField>

    <ParamField body="hours" type="number" required>
      Hours of training the employee received: over 0, with up to 1 decimal.
    </ParamField>

    <ParamField body="client_row_id" type="string">
      Your own reference for the record, up to 255 characters, echoed in errors and in the answer.
    </ParamField>
  </Expandable>
</ParamField>

## Response

`201 Created` with `items`: one per record, in the order sent, with its `source_row_index`, `client_row_id` and the
created `training`. Its `id` is the one [Delete Workforce Trainings (v2)](/api-reference/own-workforce/trainings-delete-v2)
takes.

## Errors

[Problem details](/api-reference/errors). Per-record codes in `errors[].error_code` of a `422 BULK_RECORDS_REJECTED`:

| `error_code` | Cause |
| - | - |
| `SCHEMA_INVALID` | The record does not match the schema: a missing or unknown field (e.g. `organization_id`), an empty `description` or `type`, `hours` not over 0 or with more than 1 decimal, a date out of range, `end_date` before `start_date`... `error_params.errors` says where. |
| `REFERENCE_NOT_FOUND` | `own_workforce_id` does not exist or is outside your organization's perimeter (both answer the same). |

Request-level errors: `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](/api-reference/rate-limits).

## Limits

| Limit | Value |
| - | - |
| Records per request | 1 to 1,000 |

## Related Endpoints

<CardGroup cols={2}>
  <Card title="Delete Workforce Trainings (v2)" icon="trash" href="/api-reference/own-workforce/trainings-delete-v2">
    Delete trainings by id
  </Card>

  <Card title="List Workforce Employees" icon="users" href="/api-reference/own-workforce/list">
    The employees, and their ids
  </Card>

  <Card title="List Workforce Trainings" icon="table" href="/api-reference/own-workforce/list-trainings">
    The trainings stored
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.