# Crear (/docs/developers/api-reference/custom-fields/employees/create)

## Descripcion general [#descripcion-general]

Este endpoint permite crear una o varias definiciones de campos personalizados de empleados. Acepta tanto **un objeto** como un **arreglo de hasta 100 objetos**, y la respuesta refleja la forma del cuerpo enviado. Cada fila debe enviar `name`, `key` y `type`. Una solicitud exitosa devuelve `201` con las definiciones creadas, incluyendo los timestamps generados por el servidor (`created_at`, `updated_at`).

En modo lote, si **cualquier** fila falla la validación, **ninguna** definición se crea — la respuesta lista los problemas por fila para corregirlos en una sola pasada.

`key` es el identificador público de la definición y se usa como segmento de URL en `PATCH` y `DELETE`, además de ser referenciado por integraciones de nómina. Usa solo minúsculas, números y guiones bajos. Debe iniciar con letra. Máx. 60 caracteres y único por organización.

**Claves reservadas.** Como los valores de campos personalizados se envían como claves planas en la raíz del cuerpo del empleado (junto a `first_name`, `rfc`, `department_code`, etc.), el `key` que elijas no puede colisionar con un campo nativo del empleado. Si lo hace, la solicitud falla con `400`.

`type` determina en qué columna tipada de `employee_custom_field_value` se almacenan los valores. Cuando `type === "select"`, debes enviar `select_options` con al menos una opción no vacía; para cualquier otro tipo, `select_options` se ignora.

## Endpoint [#endpoint]

`POST https://app.integranom.com/api/v1/employee-custom-fields`

## Solicitud unica — campo de texto [#solicitud-unica--campo-de-texto]

***

Envía los tres campos obligatorios. Los flags y `sort_order` toman sus valores por defecto (`false` / `0`), y `description` queda en `null`.

```json
{
  "name": "Talla de playera",
  "key": "shirt_size",
  "type": "text"
}
```

**cURL**

```bash
curl -X POST "https://app.integranom.com/api/v1/employee-custom-fields" \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "name": "Talla de playera",
  "key": "shirt_size",
  "type": "text"
}'
```

**Respuesta**

```json
{
"key": "shirt_size",
"name": "Talla de playera",
"type": "text",
"description": null,
"select_options": null,
"is_required_for_create": false,
"is_required_for_payroll": false,
"is_active": true,
"sort_order": 0,
"created_at": "2026-05-25T10:00:00.000Z",
"updated_at": "2026-05-25T10:00:00.000Z"
}
```

## Solicitud unica — tipo select con opciones [#solicitud-unica--tipo-select-con-opciones]

***

Cuando `type === "select"`, `select_options` es obligatorio y debe traer al menos una opción no vacía. Las opciones se almacenan internamente como un arreglo JSON.

```json
{
  "name": "Talla de playera",
  "key": "shirt_size",
  "type": "select",
  "select_options": ["XS", "S", "M", "L", "XL"],
  "is_required_for_create": true
}
```

**cURL**

```bash
curl -X POST "https://app.integranom.com/api/v1/employee-custom-fields" \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "name": "Talla de playera",
  "key": "shirt_size",
  "type": "select",
  "select_options": ["XS", "S", "M", "L", "XL"],
  "is_required_for_create": true
}'
```

**Respuesta**

```json
{
"key": "shirt_size",
"name": "Talla de playera",
"type": "select",
"description": null,
"select_options": ["XS", "S", "M", "L", "XL"],
"is_required_for_create": true,
"is_required_for_payroll": false,
"is_active": true,
"sort_order": 0,
"created_at": "2026-05-25T10:00:00.000Z",
"updated_at": "2026-05-25T10:00:00.000Z"
}
```

## Solicitud por lote (1–100 definiciones) [#solicitud-por-lote-1100-definiciones]

***

Envía un **arreglo** de hasta 100 definiciones en el mismo endpoint. Si **cualquier** fila falla la validación, **ninguna** definición se crea y la respuesta lista los problemas por fila.

```json
[
  { "name": "Talla de playera", "key": "shirt_size", "type": "text" },
  { "name": "Alérgenos", "key": "allergens", "type": "text", "description": "Listado libre." },
  { "name": "Apto para guardia", "key": "fit_for_duty", "type": "boolean" }
]
```

**cURL**

```bash
curl -X POST "https://app.integranom.com/api/v1/employee-custom-fields" \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '[
  { "name": "Talla de playera", "key": "shirt_size", "type": "text" },
  { "name": "Alérgenos", "key": "allergens", "type": "text", "description": "Listado libre." },
  { "name": "Apto para guardia", "key": "fit_for_duty", "type": "boolean" }
]'
```

**Respuesta**

```json
{
"summary": { "total": 3, "created": 3 },
"created": [
  {
    "key": "shirt_size",
    "name": "Talla de playera",
    "type": "text",
    "description": null,
    "select_options": null,
    "is_required_for_create": false,
    "is_required_for_payroll": false,
    "is_active": true,
    "sort_order": 0,
    "created_at": "2026-05-25T10:00:00.000Z",
    "updated_at": "2026-05-25T10:00:00.000Z"
  },
  {
    "key": "allergens",
    "name": "Alérgenos",
    "type": "text",
    "description": "Listado libre.",
    "select_options": null,
    "is_required_for_create": false,
    "is_required_for_payroll": false,
    "is_active": true,
    "sort_order": 0,
    "created_at": "2026-05-25T10:00:00.000Z",
    "updated_at": "2026-05-25T10:00:00.000Z"
  },
  {
    "key": "fit_for_duty",
    "name": "Apto para guardia",
    "type": "boolean",
    "description": null,
    "select_options": null,
    "is_required_for_create": false,
    "is_required_for_payroll": false,
    "is_active": true,
    "sort_order": 0,
    "created_at": "2026-05-25T10:00:00.000Z",
    "updated_at": "2026-05-25T10:00:00.000Z"
  }
]
}
```

## Reglas de los campos [#reglas-de-los-campos]

| Campo                     | Tipo                                                  | Requerido   | Notas                                                                                                                                                                                                                               |
| ------------------------- | ----------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                    | string                                                | Sí          | No vacío tras recortar. Etiqueta legible.                                                                                                                                                                                           |
| `key`                     | string                                                | Sí          | Identificador público. Usa solo minúsculas, números y guiones bajos. Debe iniciar con letra. Máx. 60 caracteres. Único por organización. Usado como segmento de URL en `PATCH`/`DELETE` y referenciado por integraciones de nómina. |
| `type`                    | `text` \| `number` \| `date` \| `boolean` \| `select` | Sí          | Tipo de almacenamiento. Determina en qué columna tipada de `employee_custom_field_value` viven los valores.                                                                                                                         |
| `description`             | string                                                | No          | Pista interna mostrada en interfaces administrativas.                                                                                                                                                                               |
| `select_options`          | string\[]                                             | Condicional | Obligatorio cuando `type === "select"` (mínimo 1 opción no vacía). Ignorado para cualquier otro tipo.                                                                                                                               |
| `is_required_for_create`  | boolean                                               | No          | Por defecto `false`. Cuando es `true`, `POST /api/v1/employees` rechaza filas que no envíen valor para este campo.                                                                                                                  |
| `is_required_for_payroll` | boolean                                               | No          | Por defecto `false`. Bandera informativa consumida por las exportaciones de nómina.                                                                                                                                                 |
| `is_active`               | boolean                                               | No          | Por defecto `true`.                                                                                                                                                                                                                 |
| `sort_order`              | integer ≥ 0                                           | No          | Por defecto `0`. Usado para ordenar definiciones en selectores.                                                                                                                                                                     |

## Forma de las respuestas [#forma-de-las-respuestas]

### `201` — solicitud única [#201--solicitud-única]

Cuando el cuerpo es **un objeto**, la respuesta es el objeto de la definición creada.

```json
{
  "key": "shirt_size",
  "name": "Talla de playera",
  "type": "select",
  "description": null,
  "select_options": ["XS", "S", "M", "L", "XL"],
  "is_required_for_create": true,
  "is_required_for_payroll": false,
  "is_active": true,
  "sort_order": 0,
  "created_at": "2026-05-25T10:00:00.000Z",
  "updated_at": "2026-05-25T10:00:00.000Z"
}
```

### `201` — solicitud por lote [#201--solicitud-por-lote]

Cuando el cuerpo es **un arreglo**, la respuesta envuelve los resultados en `summary` + `created[]`.

```json
{
  "summary": { "total": 3, "created": 3 },
  "created": [
    { "key": "shirt_size", "name": "Talla de playera", "...": "..." },
    { "key": "allergens", "name": "Alérgenos", "...": "..." },
    { "key": "fit_for_duty", "name": "Apto para guardia", "...": "..." }
  ]
}
```

## Errores [#errores]

### `400` — validación fallida (solicitud única) [#400--validación-fallida-solicitud-única]

Para fallas de validación de esquema (campo faltante, tipo incorrecto), `errors` es un objeto indexado por campo:

```json
{
  "message": "Validation failed.",
  "errors": {
    "select_options": ["Agrega al menos una opción para campos tipo selección."]
  }
}
```

#### Clave reservada por un campo nativo [#clave-reservada-por-un-campo-nativo]

Si el `key` que envías colisiona con un campo nativo del empleado (`first_name`, `rfc`, `notes`, `department_code`, etc.), la validación responde con:

```json
{
  "message": "Validation failed.",
  "errors": {
    "key": ["Esta clave está reservada por un campo nativo del empleado. Usa otra clave."]
  }
}
```

### `400` — validación fallida (solicitud por lote) [#400--validación-fallida-solicitud-por-lote]

Reporte por fila. **Ninguna** definición del lote fue creada.

```json
{
  "message": "Validation failed for 2 employee custom field(s). No fields were created.",
  "summary": { "total": 3, "with_errors": 2 },
  "errors": [
    {
      "index": 0,
      "employee_custom_field": { "name": "Talla de playera", "key": "shirt_size" },
      "issues": [
        {
          "field": "key",
          "code": "DUPLICATE_KEY",
          "message": "An employee custom field with key 'shirt_size' already exists in this organization."
        }
      ]
    },
    {
      "index": 2,
      "employee_custom_field": { "name": "Talla", "key": "shirt_size" },
      "issues": [
        {
          "field": "key",
          "code": "DUPLICATE_KEY_IN_BATCH",
          "message": "Key 'shirt_size' appears multiple times in this batch."
        }
      ]
    }
  ]
}
```

* `index` — posición 0-based de la fila que falló dentro del arreglo enviado.
* `employee_custom_field.name` / `employee_custom_field.key` — extraídos de tu entrada para que identifiques la fila sin contar.
* `issues[]` — todos los problemas de esa fila listados juntos para corregirlos antes de reintentar.

### `409` — condición de carrera [#409--condición-de-carrera]

Otra solicitud reclamó el mismo `key` entre el momento en que validamos el tuyo y el momento en que escribimos. En modo lote, la transacción completa hace rollback — ninguna definición es creada. Reintenta la misma solicitud.

## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error]

Estos códigos aparecen dentro de `issues[].code` (o como `code` en el cuerpo de error en single) y son estables — tu cliente puede ramificar lógica con base en ellos:

| Codigo                    | Significado                                                                                                                                 |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `INVALID_INPUT`           | Falla genérica de validación de esquema (`type` fuera del enum, `key` mal formada, `select_options` ausente con `type === "select"`, etc.). |
| `DUPLICATE_KEY`           | El `key` ya existe en otra definición de la organización.                                                                                   |
| `DUPLICATE_KEY_IN_BATCH`  | El mismo `key` aparece en más de una fila del lote.                                                                                         |
| `CONCURRENT_MODIFICATION` | Señal de reintento por condición de carrera (respuesta `409`).                                                                              |