TRUCKDOCK360 API
Plataforma de gestión de muelles
Contenido

Guía de integración — API pública de TRUCKDOCK360 (v1)

API de solo lectura para conectar el ERP, TMS, Power BI o Excel del generador a sus propios datos. Base: https://api.truckdock.co/v1. Documentación interactiva: https://api.truckdock.co/v1/docs.

1. Autenticación

  1. Un administrador crea la llave en Configuración → API (nombre, alcances, vencimiento opcional). La llave completa (td_live_…) se muestra una sola vez: guárdala en el gestor de secretos de tu sistema.
  2. Envíala en cada llamada en la cabecera X-Api-Key (también se acepta Authorization: Bearer td_live_…).
  3. Pruébala:
curl -H "X-Api-Key: td_live_TU_LLAVE" https://api.truckdock.co/v1/me

Alcances: despachos:read (despachos, entregas, transportadoras), citas:read (citas, muelles, eventos), liquidaciones:read (liquidaciones, PDF, el flete/piso SICETAC del despacho y los costos de las operaciones portuarias y aéreas), metricas:read (métricas del tablero) y metricas_dinero:read (métricas de dinero; requiere el anterior). Sin el alcance, la respuesta es 403 insufficient_scope. Revocar una llave la apaga de inmediato; las integraciones que la usen empiezan a recibir 401 api_key_revoked.

2. Formato

3. Paginación

Cada lista paginada ({data, next_cursor, next}) acepta limit (por defecto 100, máximo 500) y cursor. /muelles, /transportadoras y /citas/{id}/eventos devuelven el conjunto completo en { "data": [ … ] }, sin limit ni cursor. La primera llamada va sin cursor; las siguientes usan el next_cursor de la respuesta anterior (o directamente la URL next). El cursor es opaco y está atado a los filtros: si cambias los filtros, vuelve a empezar sin cursor (si no, recibes 400 invalid_cursor). El orden es estable por (updated_at, id) (eventos: (created_at, id)), así que las filas nuevas nunca desplazan páginas ya leídas.

4. Sincronización incremental (ERP / TMS)

  1. Primera carga: recorre la lista completa con desde/hasta por tramos (por ejemplo, un mes) siguiendo next.
  2. Guarda el mayor updated_at recibido como marca de agua.
  3. Cada ciclo: pide ?updated_since=<marca − 5 minutos> (ISO con zona horaria, por ejemplo 2026-09-22T14:00:00-05:00) y haz upsert por id. Los 5 minutos cubren transacciones largas; el upsert absorbe los repetidos.
  4. Eventos logísticos: el registro original nunca cambia, pero el generador puede corregir su fecha y hora o su ubicación (v1.4.0). Usa updated_since en /eventos con la misma regla (la marca de agua es el mayor entre registrado_at y corregido_at): trae los eventos nuevos y los corregidos desde la marca. created_since sigue sirviendo si solo te interesan los eventos nuevos.
  5. Borrados: un despacho nunca desaparece de la lista una vez expuesto. Las solicitudes que entran por los portales de proveedor y cliente aparecen cuando el generador las aprueba o rechaza; mientras son borrador no se exponen.
  6. Precisión: los instantes van truncados al segundo y updated_since es inclusivo (>=), así que usar la marca exacta re-entrega como mucho las filas de ese mismo segundo; nunca pierde filas.

5. Recursos

Recurso Alcance Fecha de desde/hasta Sincronización
/despachos, /despachos/{id} (con entregas y cita) despachos:read fecha_operacion updated_since
/entregas, /entregas/{id} despachos:read fecha_entrega updated_since
/citas, /citas/{id} citas:read día Bogotá de programada_at updated_since
/citas/{id}/eventos citas:read — (todos los eventos de la cita) —
/eventos citas:read día Bogotá de registrado_at updated_since (nuevos y corregidos) o created_since (solo nuevos)
/muelles, /transportadoras citas:read / despachos:read — —
/liquidaciones, /{id}, /{id}/pdf liquidaciones:read fecha_operacion del despacho updated_since
/operaciones-logisticas, /{id} (con lineas) liquidaciones:read fecha_operacion de la operación updated_since
/servicios-portuarios, /{id} liquidaciones:read fecha_operacion del servicio updated_since
/metricas, /metricas/{key} metricas:read (+ metricas_dinero:read) rango obligatorio si la métrica lo requiere —

Filtros de listas: estado (varios separados por coma), tipo_operacion (salida | entrada), responsable, transportadora_id, placa, muelle_id, despacho_id, cliente_nit, periodo (YYYY-MM), operacion_id; en liquidaciones, contexto (origen | destino); en costos portuarios, tipo (maritimo | aereo), agente_id y asociado. Los campos exactos de cada recurso están en la documentación interactiva.

valor_flete y sicetac_valor_piso del despacho llegan en null si la llave no tiene liquidaciones:read.

Despachos anulados. Un despacho anulado queda con estado = cancelado, anulado = true y anulado_at (el instante de la anulación); un cancelado corriente trae anulado = false. Si el transporte era contratado y el camión ya estaba en planta, la anulación genera una liquidación en borrador con cierre = anulacion: cuenta solo el tiempo logístico hasta la anulación (fin_conteo_at), su salida_at es null y el generador la valida antes de emitirla. Las demás liquidaciones traen cierre = salida y fin_conteo_at igual a salida_at.

Flujo en destino (v1.3.0). Cada entrega trae estado_destino: pendiente → llegada (el camión llegó al destino) → post_operacion (se finalizó la operación y faltan los documentos de soporte) → completado. El despacho trae el agregado: pendiente (ninguna entrega con llegada), en_curso o completado (todas completadas). Lo tienen las salidas sin nodo con transporte contratado o flota propia y el nodo puerto_cliente; en los demás llega null. Cada fase registrada mueve updated_at, así que updated_since basta para seguir el avance.

Registros corregidos (v1.4.0). Un administrador o un supervisor puede corregir la fecha y hora o la ubicación de un evento del muelle, con motivo obligatorio; el registro original se conserva en TruckDock. La API entrega siempre el valor vigente: en /eventos y /citas/{id}/eventos el evento corregido trae ocurrido_at, diferencia_min, llegada_clasificacion, latitud y longitud ya corregidos, corregido = true y corregido_at (la última corrección). Los tiempos de /citas y de la cita del despacho se recalculan con esos valores, y la corrección mueve updated_at de la cita y del despacho, así que updated_since los re-entrega. Si la corrección llega después de pagar la liquidación, el comprobante no cambia: trae corregida_despues_pago_at para conciliarlo.

Liquidación en destino (v1.5.0). Cada liquidación trae contexto: origen es el comprobante de siempre (el tiempo en el muelle del generador, uno por despacho); destino es el comprobante del tiempo en el destino de una entrega (entrega_id, entrega_numero), uno por entrega de las salidas con transporte contratado y del nodo puerto_cliente. En destino, cita_at es la cita de destino vigente, inicio_efectivo_at el mayor entre la llegada y esa cita, fin_operacion_at y fin_conteo_at son «Finalizar operación» (cierre = finalizacion), y inicio_operacion_at, salida_at y espera_min van en null; cita_id sigue siendo la cita del muelle del despacho (null en puerto_cliente). La penalidad y el stand-by se miden sobre el tiempo acumulado del viaje: minutos_previos trae lo acumulado antes del tramo (origen y destinos anteriores, sin tránsito) y penalidad_valor es la marginal del tramo. Un comprobante con requiere_nueva_cita = true (llegada tarde o entrega sin cita) trae total_min = 0 y sigue en borrador hasta que el generador reprograme la cita. Si tu integración suponía una liquidación por despacho, filtra con contexto=origen o agrupa por despacho_id.

Costos de operaciones portuarias y aéreas

Una operación logística (CA-AAAA-NNNN) junta los servicios que cobran los agentes portuarios (servicio marítimo, SM-AAAA-NNNN) o aéreos (servicio aéreo, SA-AAAA-NNNN) con los despachos terrestres de empresas contratadas que llevan la mercancía al puerto o aeropuerto, o la traen de allí. Todo con el alcance liquidaciones:read.

Cómo se calcula el costo. costo_total = costo_terrestre + costo_servicios, donde costo_terrestre es la suma del flete pactado de los despachos (un despacho cancelado aporta 0) y costo_servicios la suma de los servicios. Cada línea trae en costo lo que aporta al total. El tiempo logístico liquidado del despacho viaja en valor_tiempo_liquidado solo como información: no entra al costo de la operación. El KPI de costo de transporte del tablero (costo_transporte_cop) sí suma flete + liquidaciones: las dos cifras miden cosas distintas y no tienen por qué coincidir.

Sincronización. El updated_at de una operación se mueve cuando cambian sus totales (un flete, el costo de un servicio, una cancelación) o cuando entra o sale una línea. Un cambio de estado de un despacho que no toca el total no la mueve: sincroniza el estado de los despachos por /despachos?updated_since=… y las liquidaciones por /liquidaciones. Una operación o un servicio libre sí pueden eliminarse en la app: la API no publica borrados, así que concilia recorriendo la lista completa de vez en cuando. Al eliminar una operación, sus despachos y servicios se re-entregan con operacion_id = null.

curl -s -H "X-Api-Key: $KEY" "https://api.truckdock.co/v1/operaciones-logisticas/<id>"
{
  "data": {
    "id": "5b0e…",
    "numero": "CA-2026-0007",
    "tipo": "maritimo",
    "fecha_operacion": "2026-09-22",
    "costo_terrestre": 2800000.0,
    "costo_servicios": 4150000.5,
    "costo_total": 6950000.5,
    "moneda": "COP",
    "num_despachos": 1,
    "num_servicios": 1,
    "observaciones": null,
    "created_at": "2026-09-22T15:10:04Z",
    "updated_at": "2026-09-23T13:42:51Z",
    "lineas": [
      {
        "tipo_linea": "despacho",
        "id": "9f31…",
        "numero": "D-260922-9F31",
        "tipo_operacion_codigo": "planta_puerto",
        "tipo_operacion": "Transporte a puerto",
        "nodo": "planta_puerto",
        "fecha_operacion": "2026-09-22",
        "estado": "completado",
        "transportadora_nombre": "Transportes del Pacífico",
        "valor_flete": 2800000.0,
        "valor_tiempo_liquidado": 337596.0,
        "costo": 2800000.0
      },
      {
        "tipo_linea": "servicio",
        "id": "c7d2…",
        "numero": "SM-2026-0015",
        "tipo_operacion_codigo": "maritimo",
        "tipo_operacion": "Servicio marítimo",
        "fecha_operacion": "2026-09-22",
        "agente_nombre": "Agencia Marítima del Pacífico S.A.S.",
        "agente_nit": "900123456",
        "agente_nit_dv": "8",
        "descripcion": "Descargue, almacenaje y trámite aduanero",
        "costo": 4150000.5
      }
    ]
  }
}

(Respuesta recortada: cada línea trae todos los campos del esquema, en null los que no aplican a su tipo.)

6. Métricas (Power BI / Excel)

GET /metricas lista el catálogo (clave, definición, unidad, dimensiones y agregaciones permitidas, si es de dinero). GET /metricas/{key}?desde&hasta[&dimension&granularidad&agregacion&muelle&transportadora&vehiculo&tipo_operacion&responsable&comparar&limit&detalle] devuelve tablas planas listas para pegar en un modelo:

El rango máximo es de 366 días; las métricas de dinero exigen metricas_dinero:read. limit (por defecto y máximo 100) acota las filas de una métrica agrupada o de lista, sin cursor: si un desglose supera 100 grupos, acótalo con filtros o con un rango más corto. Límite: 30 llamadas por minuto por llave (cuentan también en el límite general de 120). Las métricas con datos personales de conductores o mecánica interna de asignación no se exponen (responden 404).

7. Power BI

Obtener datos → Web → Avanzado: URL https://api.truckdock.co/v1/despachos?desde=2026-01-01&limit=500, cabecera X-Api-Key con la llave. Para la actualización programada en el servicio de Power BI usa una URL base fija con Web.Contents(Base, [RelativePath, Query]) y guarda la llave como credencial Web API (ApiKeyName), nunca dentro del código M. Consulta completa (Editor avanzado):

let
    BaseUrl = "https://api.truckdock.co/v1",
    GetPage = (cursor as nullable text) =>
        Json.Document(Web.Contents(BaseUrl, [
            RelativePath = "despachos",
            Query = [desde = "2026-01-01", hasta = "2026-12-31", limit = "500"]
                    & (if cursor = null then [] else [cursor = cursor]),
            ApiKeyName = "X-Api-Key",
            Headers = [Accept = "application/json"]
        ])),
    Pages = List.Generate(
        () => GetPage(null),
        each _ <> null,
        each if _[next_cursor] = null then null else GetPage(_[next_cursor]),
        each _[data]),
    Tabla = Table.FromRecords(List.Combine(Pages), null, MissingField.UseNull)
in
    Tabla

Para las métricas: RelativePath = "metricas/tiempo_operacion_min", Query = [desde = "…", hasta = "…", dimension = "muelle"] y toma Json.Document(...)[data] (las filas ya vienen en data; columnas describe sus campos).

Convierte *_at con DateTimeZone.SwitchZone(DateTimeZone.FromText(x), -5) para ver hora de Bogotá.

8. Excel

Datos → Obtener datos → Desde la web → Opciones avanzadas, misma URL y cabecera X-Api-Key. Para más de una página pega la consulta M del punto 7 en el Editor avanzado de Power Query.

9. Ejemplos

# Despachos de septiembre, primera página
curl -s -H "X-Api-Key: $KEY" "https://api.truckdock.co/v1/despachos?desde=2026-09-01&hasta=2026-09-30&limit=200"

# Sincronización incremental de citas
curl -s -H "X-Api-Key: $KEY" "https://api.truckdock.co/v1/citas?updated_since=2026-09-22T13:55:00-05:00"

# Costos portuarios: operaciones marítimas de septiembre
curl -s -H "X-Api-Key: $KEY" "https://api.truckdock.co/v1/operaciones-logisticas?desde=2026-09-01&hasta=2026-09-30&tipo=maritimo"

# PDF de una liquidación
curl -s -H "X-Api-Key: $KEY" -o LIQ.pdf "https://api.truckdock.co/v1/liquidaciones/<id>/pdf"
import httpx

BASE, KEY = "https://api.truckdock.co/v1", "td_live_..."
with httpx.Client(headers={"X-Api-Key": KEY}, timeout=30) as c:
    url = f"{BASE}/despachos?desde=2026-09-01&hasta=2026-09-30&limit=500"
    while url:
        body = c.get(url).raise_for_status().json()
        for d in body["data"]:
            print(d["numero"], d["estado"], d["kg_brutos"])
        url = body["next"]

10. Límites y errores

Límite Valor
Solicitudes por minuto por llave 120 (30 para métricas)
Filas por página 500 (listas) · 100 (métricas, sin cursor)
Rango de una métrica 366 días
Llaves activas por empresa 20

Cada respuesta trae X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset; al exceder recibes 429 rate_limited con Retry-After (segundos). Repite con espera exponencial ante 429 y 5xx; no repitas ante 4xx.

11. Versionado

Ver Cambios de la API. En v1 solo se añade; los cambios incompatibles salen en /v2 y v1 sigue viva al menos 12 meses con las cabeceras Deprecation y Sunset.