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

# APIs de Importador (Beta)

> Carga en lote Pedidos, Proyectos, Tickets y Archivos en Tread con endpoints de ingesta asincrónica, luego verifica los resultados después.

Las APIs de Importador cargan datos en lote en Tread. Envía un array de Pedidos, Proyectos, Tickets o Archivos en una sola solicitud en lugar de llamar al REST API registro por registro. Cada endpoint guarda tu payload y lo procesa en el fondo.

Usa las APIs de Importador para cargas en lote — sembrando datos históricos, una sincronización nocturna desde tu ERP o sistema de contabilidad, o un feed de datos de alto volumen. Usa los endpoints del REST API en otro lugar de esta referencia cuando estés creando o actualizando un registro individual.

<Note>
  Esta API está en **Beta**. El comportamiento puede cambiar. Contacta a [developers@tread.io](mailto:developers@tread.io) con preguntas o comentarios.
</Note>

## URL base y autenticación

Cada endpoint de Importador vive bajo el mismo host que el resto de la API, con un prefijo `/ingest`.

```
https://api.tread-horizon.com/ingest
```

La autenticación usa el mismo token bearer que el resto de la API. Consulta [Autenticación](/es/api-reference/introduction#autenticación) para saber cómo obtener uno.

```bash theme={null}
Authorization: Bearer <access_token>
```

`Content-Type` depende del endpoint. Pedidos, Proyectos y Tickets toman JSON:

```
Content-Type: application/json
```

Archivos toma una carga multipart en su lugar — consulta [Archivos](#archivos).

Cada payload JSON puede incluir un objeto `metadata` opcional para tu propio seguimiento, como un ID de lote o nombre del sistema de origen. Tread lo almacena pero no actúa sobre él.

```json theme={null}
{
  "orders": [ /* ... */ ],
  "metadata": { "source_system": "ERP", "batch_id": "2025-06-02-001" }
}
```

## El modelo asincrónico

Pedidos, Proyectos y Tickets se procesan asincrónicamente. Una respuesta `202 Accepted` significa que Tread guardó tu payload y lo puso en cola para procesamiento. **No significa que el registro haya sido creado o actualizado.**

<Warning>
  `202 Accepted` no es éxito. Siempre verifica el estado del registro importado después de enviar un payload.
</Warning>

Dos formas de verificar un payload después de enviarlo:

* **Reports > API Integrations** en la app Tread. Muestra Imported For, Import Received At, State, Last Error, y el payload sin procesar que enviaste. Por defecto los últimos 7 días, los más recientes primero.
* **Webhooks de error**, si has configurado uno. Tread empuja un evento cuando un payload falla al procesarse. Consulta [Webhooks](/es/integrations/webhooks).

Cada registro importado termina en uno de estos estados:

| Estado           | Significado                                                                    |
| ---------------- | ------------------------------------------------------------------------------ |
| `created`        | Recibido. Aún no procesado.                                                    |
| `processed`      | Éxito.                                                                         |
| `failed`         | Falló. Reintentos pendientes.                                                  |
| `exhausted`      | Falló. Todos los reintentos agotados.                                          |
| `skipped`        | Una regla de negocio lo bloqueó — por ejemplo, un payload duplicado.           |
| `skipped_update` | Una actualización se omitió porque un usuario editó ese campo en la app Tread. |

Si un registro está `failed` o `exhausted`, lee su campo `last_error` para la razón, corrige los datos de origen, y reenvía. Tread desduplicará automáticamente, así que reenviar el mismo payload es seguro.

El endpoint de Archivos es la única excepción — procesa sincrónicamente. Consulta [Archivos](#archivos).

## Pedidos

`POST /ingest/orders` crea o actualiza [Pedidos](/es/concepts/orders-projects). La clave raíz es `orders` — envía un array, incluso para un registro individual.

### Campos requeridos

| Campo                  | Notas                                                                                       |
| ---------------------- | ------------------------------------------------------------------------------------------- |
| `external_id`          | Tu ID del sistema para este pedido (Ext. ID — usado para coincidir en solicitudes futuras). |
| `load_at`              | Fecha y hora de carga, ISO 8601. Interpretado en la zona horaria de la empresa.             |
| `customer.external_id` | Tu ID del sistema para el cliente que realiza el pedido.                                    |

### Solicitud de ejemplo

```bash theme={null}
curl -X POST https://api.tread-horizon.com/ingest/orders \
  -H "Authorization: Bearer $TREAD_TOKEN" \
  -H "Content-Type: application/json" \
  -d @payload.json
```

```json theme={null}
{
  "orders": [
    {
      "external_id": "ORD-2025-0142",
      "load_at": "2025-06-02T07:30:00",
      "customer": { "external_id": "CUST-441", "name": "Acme Aggregates" },
      "material": { "external_id": "MAT-GRAVEL-34" },
      "sites": [
        { "waypoint": "pickup", "external_id": "SITE-QUARRY-1", "full_address": "4500 Quarry Rd, Denver, CO" },
        { "waypoint": "drop_off", "external_id": "SITE-JOB-19", "full_address": "800 Main St, Denver, CO" }
      ],
      "truck_count": 4,
      "service": "Hired Truck"
    }
  ]
}
```

### Notas de comportamiento

* La coincidencia es por `external_id`, limitada a tu empresa. Una coincidencia actualiza los campos raíz del pedido — nombre, cantidades, tarifas, notas, y más.
* `customer`, `material`, `equipment_type`, y `sites` son registros anidados. Tread los crea si no existen pero no los actualiza en una coincidencia — consulta [coincidencia external\_id](#coincidencia-external_id-y-comportamiento-de-upsert).
* `foremen` y `vendors` son arrays. Cada payload **reemplaza** el array completo — no se fusiona con lo que ya está allí. Esto es verdad incluso si omites el campo: omitir `foremen` no preserva los foremen existentes. Envía el conjunto completo cada vez.
* Si un pedido se cancela y luego envías un payload con el mismo `external_id` en un estado no cancelado, Tread renombra el `external_id` del pedido cancelado a `X-{N}` y crea un nuevo pedido. Esto es automático — no se necesita limpieza de tu parte.

## Proyectos

`POST /ingest/projects` crea o actualiza [Proyectos](/es/concepts/orders-projects). La clave raíz es `projects`.

### Campos requeridos

| Campo                  | Notas                                 |
| ---------------------- | ------------------------------------- |
| `external_id`          | Tu ID del sistema para este proyecto. |
| `customer.external_id` | Tu ID del sistema para el cliente.    |

### Solicitud de ejemplo

```bash theme={null}
curl -X POST https://api.tread-horizon.com/ingest/projects \
  -H "Authorization: Bearer $TREAD_TOKEN" \
  -H "Content-Type: application/json" \
  -d @payload.json
```

```json theme={null}
{
  "projects": [
    {
      "external_id": "PRJ-2025-014",
      "name": "Highway 401 Resurfacing",
      "effective_at": "2025-05-01T00:00:00",
      "customer": { "external_id": "CUST-441", "name": "Acme Aggregates" },
      "phases": [
        { "name": "Mobilization", "code": "PH-01" },
        { "name": "Paving", "code": "PH-02" }
      ],
      "materials": [
        { "external_id": "MAT-ASPHALT-12", "total_quantity": 2500, "unit_of_measure": "Ton" }
      ]
    }
  ]
}
```

<Warning>
  `effective_at` debe ser estrictamente antes de `expires_at`. Si tus datos de origen los tienen iguales — común para proyectos de un día — omite `expires_at` completamente. No lo configures para el final del día; simplemente déjalo fuera.
</Warning>

### Notas de comportamiento

* La coincidencia es por `external_id`. Una coincidencia actualiza campos raíz — nombre, fechas, notas, tarifas.
* **La sincronización de fases es destructiva.** Cada payload reemplaza la lista de fases completa. Una fase omitida del payload se elimina. Siempre envía el conjunto completo, no solo lo que cambió.
* **La sincronización de materiales es igual.** Un material omitido del payload se elimina del proyecto.
* **Los sitios necesitan una dirección.** Cada sitio necesita `full_address` o `lat`/`lon`. Si tus datos de origen solo tienen un nombre de sitio, establece `full_address` en ese nombre — Tread lo geocodifica automáticamente. Sin uno de estos, el sitio falla la validación y se descarta silenciosamente: el proyecto aún reporta éxito, pero ese recogida o entrega falta y no aparece error.
* `foremen` reemplazan en la actualización, igual que en Pedidos.
* Si el nombre de un proyecto colisiona con el nombre de otro proyecto diferente, Tread le añade `-{external_id}` para mantener ambos.

## Tickets

`POST /ingest/tickets` crea [Tickets](/es/concepts/tickets-timesheets) — el registro de prueba de carga para una Carga. La clave raíz es `tickets`.

### Campos requeridos

| Campo             | Notas                                                                     |
| ----------------- | ------------------------------------------------------------------------- |
| `ticket_number`   | Único por empresa.                                                        |
| `service_date`    | ISO 8601.                                                                 |
| `quantity`        | Cantidad numérica.                                                        |
| `unit_of_measure` | Valor enum exacto — consulta [referencia de enums](#referencia-de-enums). |
| `truck_id`        | Identifica el camión.                                                     |

### Solicitud de ejemplo

```bash theme={null}
curl -X POST https://api.tread-horizon.com/ingest/tickets \
  -H "Authorization: Bearer $TREAD_TOKEN" \
  -H "Content-Type: application/json" \
  -d @payload.json
```

```json theme={null}
{
  "tickets": [
    {
      "ticket_number": "T-88213",
      "service_date": "2025-06-02T14:10:00Z",
      "quantity": 22.4,
      "unit_of_measure": "Ton",
      "truck_id": "T-101",
      "dispatch_number": "DISP-2025-0142",
      "material": { "external_id": "MAT-GRAVEL-34" },
      "customer": { "external_id": "CUST-441" }
    }
  ]
}
```

### Notas de comportamiento

* Tread vincula un ticket a un pedido de dos formas: `tread_order_id` (directo, preferido) o `dispatch_number` más una fecha de inicio de trabajo coincidente (alternativa). Envía `tread_order_id` cuando lo tengas.
* La normalización de `unit_of_measure` es insensible a mayúsculas pero los valores exactos son más seguros. `"tons"` se convierte en `"Ton"`, pero una cadena que el sistema no reconoce puede caer silenciosamente a la predeterminada (`Ton`) en lugar de error. Usa el valor enum exacto.
* `site` es un objeto individual en Tickets — a diferencia de Pedidos y Proyectos, donde `sites` es un array.

## Archivos

`POST /ingest/files` adjunta un archivo a un [Pedido](/es/concepts/orders-projects) existente (o próximo a existir). Usa `multipart/form-data`, no JSON — todos los demás endpoints de Importador usan JSON.

### Campos requeridos

| Campo                  | Notas                                                   |
| ---------------------- | ------------------------------------------------------- |
| `file`                 | Carga binaria de archivo. Una cadena de URL se rechaza. |
| `external_id`          | El `external_id` del Pedido objetivo.                   |
| `file_attachable_type` | Actualmente solo `Order` es soportado.                  |

Opcional: `category` (`Inspection Report`, `Site Map`, `Scale Ticket (External)`, `Scale Ticket (Internal)`, `Timesheet`, o `Other`) y `description`. El tamaño máximo de archivo es 10 MB. Tipos aceptados: JPEG, PNG, WebP, GIF, y PDF.

### Solicitud de ejemplo

```bash theme={null}
curl -X POST https://api.tread-horizon.com/ingest/files \
  -H "Authorization: Bearer $TREAD_TOKEN" \
  -F "file=@/path/to/scale-ticket.jpg" \
  -F "external_id=ORD-2025-0142" \
  -F "file_attachable_type=Order" \
  -F "category=Scale Ticket (External)"
```

### Notas de comportamiento

Los archivos se procesan **sincrónicamente** — la única excepción entre las APIs de Importador.

* **`201 Created`** — el Pedido ya existe. Tread adjunta el archivo inmediatamente.
* **`202 Accepted`** — el Pedido aún no existe. Tread almacena el archivo y lo adjunta automáticamente una vez que llega un Pedido coincidente — los archivos y pedidos pueden llegar en cualquier orden. No se necesita llamada extra.
* **`422`** — el archivo falló la validación (tipo incorrecto o más de 10 MB).

## Coincidencia external\_id y comportamiento de upsert

Cada endpoint de Importador usa `external_id` — tu identificador del sistema — para decidir si crear un nuevo registro o actualizar uno existente. Está limitado por empresa y es sensible a mayúsculas.

**Registros raíz** (Pedido, Proyecto, Ticket) funcionan como era de esperar: encontrado por `external_id` → actualizar; no encontrado → crear.

**Registros anidados** (cliente, material, tipo de equipo, sitios, foremen) funcionan diferente. Tread los crea si no existen, pero generalmente no actualiza sus campos una vez coincidentes. Enviar un `name` diferente para un cliente que ya existe no lo renombra.

| Registro anidado | Coincidencia por                                        |
| ---------------- | ------------------------------------------------------- |
| Cliente          | Nombre, luego `external_id`                             |
| Material         | Nombre, luego `external_id`                             |
| Sitio            | Nombre y `external_id` juntos, luego cualquiera         |
| Tipo de equipo   | Nombre o `external_id`                                  |
| Foreman          | Correo electrónico, luego teléfono, luego `external_id` |

Para renombrar un cliente, material, u otro registro de datos maestros, usa los endpoints del REST API en otro lugar de esta referencia — no las APIs de Importador.

Los arrays siguen uno de tres patrones en actualización:

| Array                                                | Comportamiento                                                             |
| ---------------------------------------------------- | -------------------------------------------------------------------------- |
| `foremen`, `vendors`, `collaborators`, `salespeople` | Reemplazar — el array completo se intercambia                              |
| `phases`, `materials` (solo Proyectos)               | Sincronizar — los elementos faltantes del payload se eliminan              |
| `sites`                                              | Upsert — coincidencia por tipo de waypoint; el ID del registro se preserva |

## Referencia de enums

Valores exactos de cadena que Tread espera. Un valor fuera de esta lista se normaliza (unidad de medida) o se rechaza.

**Unidad de medida** — `Load`, `Tonne`, `Ton`, `Yard`, `Meter`, `Foot`, `Liter`, `Hour`, `Bushel`, `Gallon`, `CubicMeter`, `Mile`, `Kilometer`, `Barrel`, `Bag`, `Pallet`. Predeterminado: `Ton`.

**Tipo de tarifa** — `RatePerHour` (predeterminado), `RatePerDay`, `RatePerLoad`, `RatePerTon`, `RatePerTonne`, `RatePerYard`, `RatePerBushel`, `RateCommission`, `RateFlatCommission`, `RateFlatRate`.

**Waypoint del sitio** — `pickup`, `staging`, `drop_off`, `weigh_point`.

**Tipo de sitio** — `Plant`, `Quarry`, `JobSite`, `Depot`, `EquipmentHomeBase`, `Daily`, `Other` (predeterminado).

## Trampa comunes

* **`202` no significa que funcionó.** Verifica el estado del registro importado antes de tratar un payload como completo.
* **Los registros anidados no se actualizan en la coincidencia.** Renombrar un cliente o material en tu payload no tiene efecto una vez que ese registro ya existe. Usa el REST API para renombrarlo.
* **Los sitios sin dirección se descartan, silenciosamente.** Sin `full_address` o `lat`/`lon` en un sitio significa que Tread lo descarta — y el Proyecto u Orden aún reporta éxito.
* **Los números de teléfono inválidos se descartan, silenciosamente.** Un teléfono que no se normaliza a E.164 se elimina del contacto. Sin error, sin entrada de `last_error`.
* **Los payloads duplicados se omiten, no se reprocesa.** Enviar el mismo payload exacto dos veces marca el segundo como `skipped`. Esto es esperado, no un bug.
* **La sincronización de fases elimina lo que dejas fuera.** Cada payload de Proyecto reemplaza la lista de fases completa, no solo las fases que estés cambiando.
* **Los foremen y vendors reemplazan, no se fusionan.** Cada payload con un array `foremen` o `vendors` intercambia el todo, incluso si omites el campo.
* **La unidad de medida es exigente.** `"tons"` se normaliza a `"Ton"`, pero una cadena que el sistema no reconoce cae silenciosamente al predeterminado (`Ton`) en lugar de error. Usa valores enum exactos.

## Gestión de registros individuales

¿Necesitas crear, actualizar o eliminar un registro a la vez en lugar de carga en lote? Usa los endpoints del REST API en otro lugar de esta referencia. Son sincrónicos — un `201` o `200` significa que está listo — y soportan actualizaciones de campo completas en registros anidados, incluyendo renombrar un cliente o material. Las APIs de Importador son solo para cargas en lote.

## Continuar

<CardGroup cols={2}>
  <Card title="Autenticación" icon="key" href="/es/api-reference/introduction#autenticación">
    Obtén un token bearer para estos endpoints.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/es/integrations/webhooks">
    Recibe notificaciones cuando un payload importado falla al procesarse.
  </Card>

  <Card title="Pedidos y Proyectos" icon="diagram-project" href="/es/concepts/orders-projects">
    Cómo se ajusta la jerarquía de despacho.
  </Card>

  <Card title="Tickets y Partes de horas" icon="receipt" href="/es/concepts/tickets-timesheets">
    Qué registra un Ticket, y cómo se convierte en una Liquidación.
  </Card>
</CardGroup>
