*API REST › Referencia de endpoints › Alquileres › Pedidos*

# Crear un pedido

> Crea un pedido en DRAFT con el material solicitado.

Es el equivalente validado del buzón de peticiones de proyecto: permite que un formulario web, un CRM o un asistente pida material para unas fechas.

NO acepta importes de ningún tipo (ni precios, ni descuentos, ni seguro, ni fianzas): el pedido nace a cero y el equipo lo valora desde la aplicación. Tampoco acepta estado, unidades serializadas concretas, accesorios ni notas.

Cada identificador referenciado (cliente, artículos, proyecto) se resuelve contra la cuenta de la clave ANTES de escribir nada: uno solo que no pertenezca a la cuenta rechaza toda la petición con 400 y no crea nada.

`POST /api/v1/orders` — permiso `orders:write`

Permiso: `orders:write` · 120/min · Idempotency-Key · If-Match · MCP: `filmo_orders_create`

## Autorizaciones

Clave de API como Bearer, con el permiso de abajo. `orders:write`

## Cabeceras

| Nombre | Tipo | Obligatorio | Descripción |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | No | Identificador único de la petición, elegido por el cliente (sirve un UUID). Entre 1 y 255 caracteres: una cabecera vacía se rechaza con 400 `validation_error`, porque el cliente se creería protegido. Repetir la misma clave con el MISMO cuerpo en 24 h devuelve la respuesta guardada sin volver a ejecutar, con la cabecera `Idempotent-Replay: true`. Con un cuerpo DISTINTO devuelve 422. Es la forma segura de reintentar tras un tiempo de espera de red. |
| `If-Match` | string | No | Bloqueo optimista: el `ETag` que leíste del recurso (o `*`). Si el recurso ha cambiado desde entonces, la escritura se rechaza con `412 precondition_failed` antes de hacer nada; léelo de nuevo y reintenta. Sin la cabecera, la escritura siempre se realiza. |

## Cuerpo

Esquema del cuerpo: `OrderCreateBody`

| Nombre | Tipo | Obligatorio | Descripción |
| --- | --- | --- | --- |
| `contactId` | string | Sí | Identificador del cliente del pedido. Debe pertenecer a la cuenta de la clave de API. |
| `projectId` | string | No | Proyecto al que está asignado el pedido; se omite en un pedido independiente. |
| `startDate` | string<date-time> | Sí | Inicio del alquiler (ISO 8601 con desplazamiento). |
| `endDate` | string<date-time> | Sí | Fin del alquiler (ISO 8601 con desplazamiento); debe ser posterior a startDate. |
| `lines` | object[] | Sí | Material solicitado (entre 1 y 100 líneas). |
| `lines.itemId` | string | Sí | Identificador del artículo del catálogo. Debe pertenecer a la cuenta de la clave de API. |
| `lines.quantity` | integer | Sí | Unidades solicitadas del artículo. |
| `lines.variantId` | string | No | Identificador de la variante concreta; se omite para el artículo base. |

## Ejemplos de petición

*Crear un pedido*

**cURL**

```bash
curl https://app.filmo.io/api/v1/orders \
  --request POST \
  --header "Authorization: Bearer $FILMO_API_KEY" \
  --header "Idempotency-Key: $(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid)" \
  --header 'Content-Type: application/json' \
  --data '{
  "contactId": "cont_8f2a1c",
  "startDate": "2026-09-01T08:00:00Z",
  "endDate": "2026-09-05T20:00:00Z",
  "lines": [
    {
      "itemId": "item_3b91de",
      "quantity": 2
    },
    {
      "itemId": "item_7c04af",
      "quantity": 1,
      "variantId": "var_11"
    }
  ]
}'
```

**Node.js**

```javascript
fetch('https://app.filmo.io/api/v1/orders', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer YOUR_FILMO_API_KEY',
    'Idempotency-Key': crypto.randomUUID(),
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    contactId: 'cont_8f2a1c',
    startDate: '2026-09-01T08:00:00Z',
    endDate: '2026-09-05T20:00:00Z',
    lines: [
      {
        itemId: 'item_3b91de',
        quantity: 2
      },
      {
        itemId: 'item_7c04af',
        quantity: 1,
        variantId: 'var_11'
      }
    ]
  })
})
```

**Python**

```python
import uuid
import requests

requests.post("https://app.filmo.io/api/v1/orders",
    headers={
      "Authorization": "Bearer YOUR_FILMO_API_KEY",
      "Idempotency-Key": str(uuid.uuid4()),
      "Content-Type": "application/json"
    },
    json={
      "contactId": "cont_8f2a1c",
      "startDate": "2026-09-01T08:00:00Z",
      "endDate": "2026-09-05T20:00:00Z",
      "lines": [
        {
          "itemId": "item_3b91de",
          "quantity": 2
        },
        {
          "itemId": "item_7c04af",
          "quantity": 1,
          "variantId": "var_11"
        }
      ]
    }
)
```

## Respuesta

Esquema de la respuesta: `OrderDetailDto`

| Estado | Descripción |
| --- | --- |
| 201 | Respuesta correcta. |
| 400 | Petición no válida (validation_error): consulta o cuerpo mal formados. `errors[]` enumera cada campo erróneo con su código estable. |
| 401 | Fallo de autenticación (unauthorized, api_key_invalid, api_key_revoked, api_key_expired, api_key_owner_inactive, api_key_owner_not_admin). |
| 403 | No permitido (insufficient_scope: la clave de API no tiene el scope requerido, indicado en `requiredScope`; forbidden: el propietario de la clave carece del permiso que exige la operación). `insufficient_scope` se responde antes de que la petición cuente contra el límite de peticiones, así que no lleva cabeceras `RateLimit-*`; `forbidden` sí. |
| 404 | Recurso no encontrado (not_found): no existe o está fuera del alcance de la clave de API. |
| 409 | Conflicto (conflict): el estado actual del recurso impide la operación, ya hay en curso una petición con esa Idempotency-Key, o otra escritura condicional (`If-Match`) retuvo el recurso durante más de cinco segundos. `errors[0].code` indica cuál: `in_progress` y `lock_timeout` se resuelven reintentando; `invalid_state` no, porque el estado del recurso prohíbe la operación. |
| 412 | Precondición fallida (precondition_failed): la etiqueta de entidad `If-Match` no coincide con la representación actual. Lee el recurso de nuevo y reintenta. |
| 422 | Idempotency-Key reutilizada (idempotency_key_reused): la misma clave se usó antes con un cuerpo distinto. Cambia la clave. |
| 429 | Límite de peticiones superado (rate_limited): reintenta tras `Retry-After` segundos; `RateLimit-*` indica qué cuota se ha agotado. |
| 500 | Error interno (internal_error). |

**201**

| Nombre | Tipo | Descripción |
| --- | --- | --- |
| `data` | object | El pedido completo, tal como lo devuelve la API. |
| `data.id` | string | Identificador único del pedido. |
| `data.orderNumber` | string | Número de pedido legible. |
| `data.externalReference` | string | Referencia externa del pedido (por ejemplo, del sistema del cliente); null si no consta. |
| `data.status` | string | Estado bruto del pedido tal como se almacena en la base de datos (12 valores): draft, reserved, prepared_partially, prepared, picked_up_partially, picked_up, returned_partially, returned, checked_in_partially, checked_in, archived o cancelled. `archived` forma parte del conjunto almacenado, pero un pedido archivado no es visible para la API, así que no lo observarás. `draft`, `reserved`, `prepared_partially`, `prepared`, `picked_up_partially`, `picked_up`, `returned_partially`, `returned`, `checked_in_partially`, `checked_in`, `archived`, `cancelled` |
| `data.startDate` | string<date-time> | Inicio del periodo de alquiler en ISO 8601 con desplazamiento. |
| `data.endDate` | string<date-time> | Fin del periodo de alquiler en ISO 8601 con desplazamiento. |
| `data.rentalDays` | number | Número de días de alquiler facturables; null si no se ha calculado. |
| `data.projectId` | string | Identificador del proyecto al que pertenece el pedido; null si no está vinculado. |
| `data.projectName` | string | Nombre del proyecto al que pertenece el pedido; null si no está vinculado. |
| `data.contact` | object | Referencia compacta al contacto del pedido; null si no hay. |
| `data.contact.id` | string | Identificador único del contacto. |
| `data.contact.name` | string | Nombre del contacto. |
| `data.itemsCount` | integer | Número de artículos del pedido. |
| `data.subTotalPrice` | string | Subtotal antes de descuentos e impuestos, como cadena decimal "1234.50" (ROUND_HALF_UP). |
| `data.discountTotal` | string | Total de descuentos como cadena decimal "1234.50" (ROUND_HALF_UP); null si no hay descuento. |
| `data.insuranceTotal` | string | Total del seguro como cadena decimal "1234.50" (ROUND_HALF_UP). |
| `data.depositTotal` | string | Total de fianzas como cadena decimal "1234.50" (ROUND_HALF_UP). |
| `data.taxesPrice` | string | Total de impuestos como cadena decimal "1234.50" (ROUND_HALF_UP). |
| `data.netPrice` | string | Base imponible (neto antes de impuestos) como cadena decimal "1234.50" (ROUND_HALF_UP). |
| `data.grossPrice` | string | Total con impuestos, como cadena decimal "1234.50" (ROUND_HALF_UP). |
| `data.tags` | object[] | Etiquetas adjuntas al pedido, con su identificador y nombre. |
| `data.tags.id` | string | Identificador único de la etiqueta. |
| `data.tags.name` | string | Nombre de la etiqueta. |
| `data.customFields` | object<string, any> | Valores de los campos personalizados por identificador de campo personalizado; las definiciones están en `/api/v1/custom-fields`. |
| `data.source` | string | Origen desde el que se creó el pedido (por ejemplo web, mobile, admin); null si no consta. |
| `data.createdAt` | string<date-time> | Fecha de creación en ISO 8601 con desplazamiento. |
| `data.updatedAt` | string<date-time> | Fecha de la última actualización en ISO 8601 con desplazamiento. |
| `data.billingAddress` | object | Dirección de facturación del pedido; null si no hay. |
| `data.billingAddress.label` | string | Etiqueta descriptiva de la dirección; null si no consta. |
| `data.billingAddress.addressLine1` | string | Primera línea de la dirección; null si no consta. |
| `data.billingAddress.addressLine2` | string | Segunda línea de la dirección; null si no consta. |
| `data.billingAddress.city` | string | Ciudad; null si no consta. |
| `data.billingAddress.province` | string | Provincia o región; null si no se ha registrado. |
| `data.billingAddress.postcode` | string | Código postal; null si no se ha registrado. |
| `data.billingAddress.country` | string | País; null si no consta. |
| `data.shippingAddress` | object | Dirección de envío del pedido; null si no hay. |
| `data.shippingAddress.label` | string | Etiqueta descriptiva de la dirección; null si no consta. |
| `data.shippingAddress.addressLine1` | string | Primera línea de la dirección; null si no consta. |
| `data.shippingAddress.addressLine2` | string | Segunda línea de la dirección; null si no consta. |
| `data.shippingAddress.city` | string | Ciudad; null si no consta. |
| `data.shippingAddress.province` | string | Provincia o región; null si no se ha registrado. |
| `data.shippingAddress.postcode` | string | Código postal; null si no se ha registrado. |
| `data.shippingAddress.country` | string | País; null si no consta. |
| `data.contactPerson` | object | Persona de contacto vinculada al pedido; null si no hay ninguna. |
| `data.contactPerson.id` | string | Identificador único del contacto. |
| `data.contactPerson.name` | string | Nombre del contacto. |
| `data.quotes` | object[] | Referencias informativas a los presupuestos generados a partir del pedido. |
| `data.quotes.id` | string | Identificador único del documento referenciado. |
| `data.quotes.number` | string | Número del documento; null hasta que se numera. |
| `data.quotes.status` | string | Estado del documento en el momento en que se escribió la referencia; null si no consta. Informativo: puede estar desactualizado. |
| `data.invoices` | object[] | Referencias informativas a las facturas generadas a partir del pedido. |
| `data.invoices.id` | string | Identificador único del documento referenciado. |
| `data.invoices.number` | string | Número del documento; null hasta que se numera. |
| `data.invoices.status` | string | Estado del documento en el momento en que se escribió la referencia; null si no consta. Informativo: puede estar desactualizado. |
| `data.proformas` | object[] | Referencias informativas a las proformas generadas a partir del pedido. |
| `data.proformas.id` | string | Identificador único del documento referenciado. |
| `data.proformas.number` | string | Número del documento; null hasta que se numera. |
| `data.proformas.status` | string | Estado del documento en el momento en que se escribió la referencia; null si no consta. Informativo: puede estar desactualizado. |
| `meta` | object | Información adicional sobre la respuesta. |
| `meta.warnings` | object[] | Avisos del ciclo de vida; solo aparecen cuando la operación está obsoleta. |
| `meta.warnings.code` | string | Código de aviso estable. `deprecated` |
| `meta.warnings.message` | string | Para personas: qué está obsoleto, hasta cuándo y qué usar. |
| `meta.warnings.sunset` | string | Día (YYYY-MM-DD) en que la operación deja de estar disponible. |
| `meta.warnings.replacement` | string | `operationId` de la operación que hay que usar en su lugar, o null si no hay ninguna. |
| `meta.warnings.link` | string | Dónde está documentada la deprecación (el changelog). |

*Respuesta de ejemplo · 201*

```json
{
  "data": {
    "id": "order_9f3a2b",
    "orderNumber": "2026-0001",
    "externalReference": "2026-0001",
    "status": "draft",
    "startDate": "2026-09-01T08:00:00.000Z",
    "endDate": "2026-09-01T08:00:00.000Z",
    "rentalDays": 1,
    "projectId": "project_9f3a2b",
    "projectName": "Example project name",
    "contact": {
      "id": "contact_9f3a2b",
      "name": "Example contact"
    },
    "itemsCount": 1,
    "subTotalPrice": "120.00",
    "discountTotal": "120.00",
    "insuranceTotal": "120.00",
    "depositTotal": "120.00",
    "taxesPrice": "120.00",
    "netPrice": "120.00",
    "grossPrice": "120.00",
    "tags": [
      {
        "id": "tag_9f3a2b",
        "name": "Example tag"
      }
    ],
    "customFields": {
      "example": "example"
    },
    "source": "Example source",
    "createdAt": "2026-09-01T08:00:00.000Z",
    "updatedAt": "2026-09-01T08:00:00.000Z",
    "billingAddress": {
      "label": "Main",
      "addressLine1": "Calle de Ejemplo 12",
      "addressLine2": "Calle de Ejemplo 12",
      "city": "Madrid",
      "province": "Madrid",
      "postcode": "28001",
      "country": "ES"
    },
    "shippingAddress": {
      "label": "Main",
      "addressLine1": "Calle de Ejemplo 12",
      "addressLine2": "Calle de Ejemplo 12",
      "city": "Madrid",
      "province": "Madrid",
      "postcode": "28001",
      "country": "ES"
    },
    "contactPerson": {
      "id": "contact_person_9f3a2b",
      "name": "Example contact person"
    },
    "quotes": [
      {
        "id": "quote_9f3a2b",
        "number": "2026-0001",
        "status": "Example status"
      }
    ],
    "invoices": [
      {
        "id": "invoice_9f3a2b",
        "number": "2026-0001",
        "status": "Example status"
      }
    ],
    "proformas": [
      {
        "id": "proforma_9f3a2b",
        "number": "2026-0001",
        "status": "Example status"
      }
    ]
  }
}
```

## Errores

| Estado | Códigos | Significado |
| --- | --- | --- |
| 400 | `validation_error` | Consulta o cuerpo mal formados. `errors[]` enumera cada campo erróneo con su código estable. |
| 404 | `not_found` | No existe o queda fuera del alcance de la clave de API. |
| 409 | `conflict` | El estado actual del recurso impide la operación, ya hay en curso una petición con esa Idempotency-Key, u otra escritura condicional (`If-Match`) retuvo el recurso durante más de cinco segundos. `errors[0].code` indica cuál: `in_progress` y `lock_timeout` se resuelven reintentando; `invalid_state` no, porque el estado del recurso prohíbe la operación. |
| 412 | `precondition_failed` | La etiqueta de entidad `If-Match` no coincide con la representación actual. Vuelve a leer el recurso y reintenta. |
| 422 | `idempotency_key_reused` | La misma clave se usó antes con un cuerpo distinto. Cambia la clave. |

## Obsolescencia

Esta operación no está obsoleta. Las obsolescencias se anuncian con la cabecera Deprecation y una fecha de retirada.

---

URL canónica: https://docs.filmo.io/es/rest-api/endpoint-reference/orders/create-an-order  
Última actualización: 2026-10-25