{
  "openapi": "3.1.0",
  "info": {
    "title": "PorteListo API",
    "version": "1.3.0",
    "description": "v1.3: CMR (carta de porte internacional) por la API: POST/GET /cmrs y anulación; necesita el plan Completo del transportista. v1.2: sincronización incremental (changed_since, order=changed_asc y cursor), filtro por matrícula y por obra, y obras y lugares de carga con ubicación para cruzar con la telemática. Todo es opcional: sin los parámetros nuevos, las respuestas son las de antes con campos añadidos. API para crear, consultar y anular documentos de control (DeCA) y enviar albaranes desde tu programa de gestión. Dos clases de clave: de TRANSPORTISTA (Ajustes → API; planes Completo y Empresa) y de CARGADOR (gratis, en cargador.portelisto.com/programa). Con clave de cargador, cada DeCA o albarán lleva «carrier_nif» y solo se acepta si ese transportista tiene la conexión activa (incluida en su plan Completo); el cargador no ve conductores ni vehículos ni elige conductor (se pone el habitual del camión)."
  },
  "servers": [
    {
      "url": "https://app.portelisto.com/api/v1"
    }
  ],
  "security": [
    {
      "bearer": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "pl_live_… (transportista: Ajustes → API · cargador: cargador.portelisto.com/programa)"
      }
    },
    "schemas": {
      "Deca": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "number": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "borrador",
              "emitido",
              "anulado"
            ]
          },
          "version": {
            "type": "integer"
          },
          "service_date": {
            "type": "string",
            "format": "date"
          },
          "issued_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "source": {
            "type": "string"
          },
          "pdf_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "URL pública del PDF (la del QR)"
          },
          "shipper": {
            "type": "object",
            "required": [
              "name",
              "nif",
              "address"
            ],
            "properties": {
              "name": {
                "type": "string"
              },
              "nif": {
                "type": "string",
                "example": "B12345678"
              },
              "address": {
                "type": "string"
              }
            }
          },
          "carrier": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "nif": {
                "type": "string"
              }
            }
          },
          "tractor_plate": {
            "type": "string"
          },
          "trailer_plate": {
            "type": [
              "string",
              "null"
            ]
          },
          "driver": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "shipments": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "origin",
                "destination",
                "goods"
              ],
              "properties": {
                "origin": {
                  "type": "string",
                  "example": "Planta de Ejemplo, Pol. Ind. Ejemplo, 00000 Ejemplo"
                },
                "destination": {
                  "type": "string",
                  "example": "Obra calle Mayor 10, Ejemplo"
                },
                "goods": {
                  "type": "string",
                  "example": "Viguetas de hormigón"
                },
                "weight_kg": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "example": 18450
                },
                "packages": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "example": "12 paquetes"
                },
                "delivery_note_ref": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "example": "ALB-10231"
                }
              }
            }
          },
          "observations": {
            "type": [
              "string",
              "null"
            ]
          },
          "whatsapp": {
            "type": "object",
            "properties": {
              "status": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "enviado",
                  "entregado",
                  "leido",
                  "fallido",
                  "sms",
                  null
                ],
                "description": "sms: el WhatsApp falló y se envió el enlace por SMS"
              },
              "at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "changed_at": {
            "type": "string",
            "format": "date-time",
            "description": "v1.2 · Último cambio de negocio: emitir, modificar en ruta (nueva versión), anular o corregir un borrador. Los cambios de WhatsApp o del PDF no la mueven."
          },
          "worksite": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Site"
              },
              {
                "type": "null"
              }
            ],
            "description": "v1.2 · Obra (destino recurrente) del DeCA"
          },
          "service_start_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "v1.2 · Inicio real del servicio, si se conoce"
          },
          "service_end_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "v1.2 · Fin real del servicio, si se conoce"
          }
        }
      },
      "Cmr": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "number": {
            "type": "string",
            "example": "CMR-2026-000012"
          },
          "status": {
            "type": "string",
            "enum": [
              "emitido",
              "anulado"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "source": {
            "type": "string",
            "enum": [
              "panel",
              "api",
              "cargador"
            ]
          },
          "pdf_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "PDF para imprimir (3 ejemplares) y firmar"
          },
          "sender": {
            "type": "object"
          },
          "consignee": {
            "type": "object"
          },
          "pickup": {
            "type": "object"
          },
          "delivery": {
            "type": "object"
          },
          "goods": {
            "type": "string"
          },
          "packages": {
            "type": [
              "string",
              "null"
            ]
          },
          "weight_kg": {
            "type": [
              "number",
              "null"
            ]
          },
          "volume_m3": {
            "type": [
              "number",
              "null"
            ]
          },
          "documents": {
            "type": [
              "string",
              "null"
            ]
          },
          "instructions": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment": {
            "type": "string"
          },
          "special_agreements": {
            "type": [
              "string",
              "null"
            ]
          },
          "carrier": {
            "type": "object"
          },
          "tractor_plate": {
            "type": [
              "string",
              "null"
            ]
          },
          "trailer_plate": {
            "type": [
              "string",
              "null"
            ]
          },
          "driver": {
            "type": [
              "object",
              "null"
            ],
            "description": "null con clave de cargador"
          },
          "established": {
            "type": "object"
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cancel_reason": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "CmrInput": {
        "type": "object",
        "required": [
          "consignee",
          "pickup",
          "delivery",
          "goods"
        ],
        "properties": {
          "carrier_nif": {
            "type": "string",
            "description": "Solo con clave de cargador: transportista (conexión activa)"
          },
          "sender": {
            "type": "object",
            "description": "Remitente. Con clave de cargador se pone solo (su NIF)",
            "properties": {
              "name": {
                "type": "string"
              },
              "nif": {
                "type": "string"
              },
              "address": {
                "type": "string"
              },
              "country": {
                "type": "string",
                "example": "ES"
              }
            }
          },
          "consignee": {
            "type": "object",
            "required": [
              "name",
              "country"
            ],
            "properties": {
              "name": {
                "type": "string",
                "example": "Client Exemple SARL"
              },
              "nif": {
                "type": "string",
                "description": "NIF o VAT",
                "example": "FR57382743391"
              },
              "address": {
                "type": "string"
              },
              "country": {
                "type": "string",
                "example": "FR"
              }
            }
          },
          "pickup": {
            "type": "object",
            "properties": {
              "place": {
                "type": "string",
                "example": "Planta de Ejemplo, Pol. Ind. Ejemplo, 00000 Ejemplo"
              },
              "date": {
                "type": "string",
                "format": "date"
              }
            }
          },
          "delivery": {
            "type": "object",
            "properties": {
              "place": {
                "type": "string",
                "example": "Zone Industrielle, 64100 Bayonne"
              },
              "country": {
                "type": "string",
                "example": "FR",
                "description": "ISO-2"
              }
            }
          },
          "goods": {
            "type": "string",
            "example": "Viguetas pretensadas de hormigón"
          },
          "packages": {
            "type": "string",
            "example": "18 paquetes"
          },
          "weight_kg": {
            "type": "number",
            "example": 21400
          },
          "volume_m3": {
            "type": "number"
          },
          "documents": {
            "type": "string",
            "description": "Documentos anexos (casilla 5)",
            "example": "Albarán nº 004611"
          },
          "delivery_note_ref": {
            "type": "string",
            "description": "Si no envías documents: «Albarán nº …»"
          },
          "instructions": {
            "type": "string"
          },
          "payment": {
            "type": "string",
            "enum": [
              "pagados",
              "debidos"
            ]
          },
          "special_agreements": {
            "type": "string"
          },
          "tractor_plate": {
            "type": "string"
          },
          "trailer_plate": {
            "type": "string"
          },
          "driver_id": {
            "type": "string",
            "description": "Solo clave de transportista"
          },
          "driver_phone": {
            "type": "string",
            "description": "Solo clave de transportista"
          },
          "established": {
            "type": "object",
            "description": "Casilla 21 (por defecto, población y fecha de carga)",
            "properties": {
              "place": {
                "type": "string"
              },
              "date": {
                "type": "string",
                "format": "date"
              }
            }
          }
        }
      },
      "Site": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": [
              "string",
              "null"
            ],
            "example": "202115",
            "description": "Código de obra (null en lugares de carga)"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "client": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "nif": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "location": {
            "type": [
              "object",
              "null"
            ],
            "description": "Ubicación para la geocerca (null si no se conoce)",
            "properties": {
              "lat": {
                "type": "number"
              },
              "lon": {
                "type": "number"
              },
              "source": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "manual",
                  "telematica",
                  "geocoder",
                  null
                ]
              },
              "radius_m": {
                "type": [
                  "integer",
                  "null"
                ],
                "example": 150
              },
              "updated_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/decas": {
      "get": {
        "summary": "Lista de DeCA",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Fecha de servicio desde (por defecto, hace 30 días)"
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "emitido",
                "anulado",
                "borrador"
              ]
            }
          },
          {
            "name": "delivery_note_ref",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "carrier_nif",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Clave de cargador: solo los de ese transportista"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            },
            "description": "Con cursor se ignora"
          },
          {
            "name": "changed_since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "v1.2 · Solo DeCA con changed_at posterior (incluye anulados). Con este parámetro no se aplican los 30 días por defecto de from/to."
          },
          {
            "name": "plate",
            "in": "query",
            "schema": {
              "type": "string",
              "example": "1234BCD"
            },
            "description": "v1.2 · Matrícula de la tractora o del remolque (sin espacios ni guiones)"
          },
          {
            "name": "worksite",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "v1.2 · Obra: su código o su id"
          },
          {
            "name": "order",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "created_desc",
                "changed_asc"
              ],
              "default": "created_desc"
            },
            "description": "v1.2 · changed_asc: por changed_at y id, para sincronizar con cursor. Entrega los cambios con 5 s de margen."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "v1.2 · El next_cursor de la respuesta anterior (solo con order=changed_asc). Sin huecos ni repetidos."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Deca"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "v1.2 · Con order=changed_asc, si hay más: pásalo en cursor. null si no hay más (o con created_desc)."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Parámetro no válido",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      },
      "post": {
        "summary": "Crear y emitir un DeCA",
        "description": "Crea el DeCA, genera el PDF con QR y lo envía por WhatsApp al conductor. Requiere permiso «write». Tú eres responsable de que los datos sean correctos.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Si repites la petición con la misma clave, recibes la misma respuesta sin crear nada nuevo."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tractor_plate",
                  "shipments"
                ],
                "properties": {
                  "carrier_nif": {
                    "type": "string",
                    "description": "Obligatorio con clave de cargador: NIF del transportista (conectado)"
                  },
                  "service_date": {
                    "type": "string",
                    "format": "date",
                    "description": "Por defecto, hoy"
                  },
                  "shipper": {
                    "type": "object",
                    "required": [
                      "name",
                      "nif",
                      "address"
                    ],
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "nif": {
                        "type": "string",
                        "example": "B12345678"
                      },
                      "address": {
                        "type": "string"
                      }
                    },
                    "description": "Obligatorio con clave de transportista. Con clave de cargador se usa la tuya"
                  },
                  "client": {
                    "type": "object",
                    "required": [
                      "name",
                      "nif",
                      "address"
                    ],
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "nif": {
                        "type": "string",
                        "example": "B12345678"
                      },
                      "address": {
                        "type": "string"
                      }
                    }
                  },
                  "client_contracts": {
                    "type": "boolean",
                    "description": "true si el cargador contractual es el cliente (a quien se factura el porte)"
                  },
                  "tractor_plate": {
                    "type": "string",
                    "example": "1234BCD"
                  },
                  "trailer_plate": {
                    "type": "string",
                    "example": "R5678BCD"
                  },
                  "driver_id": {
                    "type": "string",
                    "description": "De GET /drivers"
                  },
                  "driver_phone": {
                    "type": "string",
                    "description": "Alternativa a driver_id"
                  },
                  "shipments": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "origin",
                        "destination",
                        "goods"
                      ],
                      "properties": {
                        "origin": {
                          "type": "string",
                          "example": "Planta de Ejemplo, Pol. Ind. Ejemplo, 00000 Ejemplo"
                        },
                        "destination": {
                          "type": "string",
                          "example": "Obra calle Mayor 10, Ejemplo"
                        },
                        "goods": {
                          "type": "string",
                          "example": "Viguetas de hormigón"
                        },
                        "weight_kg": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "example": 18450
                        },
                        "packages": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "example": "12 paquetes"
                        },
                        "delivery_note_ref": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "example": "ALB-10231"
                        }
                      }
                    },
                    "maxItems": 20
                  },
                  "worksite": {
                    "type": "object",
                    "properties": {
                      "code": {
                        "type": "string"
                      },
                      "name": {
                        "type": "string"
                      }
                    }
                  },
                  "observations": {
                    "type": "string"
                  },
                  "special_authorization": {
                    "type": "string"
                  },
                  "force_duplicate": {
                    "type": "boolean",
                    "description": "Si el nº de albarán ya tiene DeCA la API responde 409; true para emitir otro igualmente"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Emitido",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Deca"
                    },
                    "whatsapp": {
                      "type": "string",
                      "enum": [
                        "enviado",
                        "sms",
                        "sin-movil",
                        "no-configurado",
                        "manual",
                        "limite",
                        "error"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "422": {
            "description": "Datos incompletos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      }
    },
    "/decas/{id}": {
      "get": {
        "summary": "Un DeCA (por id o número)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "404": {
            "description": "No existe"
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      }
    },
    "/decas/{id}/cancel": {
      "post": {
        "summary": "Anular un DeCA emitido",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Si repites la petición con la misma clave, recibes la misma respuesta sin crear nada nuevo."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reason"
                ],
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 5
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Anulado"
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "409": {
            "description": "No está emitido"
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      }
    },
    "/delivery-notes": {
      "post": {
        "summary": "Enviar un albarán (PDF o fotos)",
        "description": "PorteListo lo lee y lo deja en «Pendientes» del panel, como un albarán recibido por email. Máximo 8 archivos y 10 MB.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Si repites la petición con la misma clave, recibes la misma respuesta sin crear nada nuevo."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "files"
                ],
                "properties": {
                  "reference": {
                    "type": "string"
                  },
                  "carrier_nif": {
                    "type": "string",
                    "description": "Obligatorio con clave de cargador"
                  },
                  "files": {
                    "type": "array",
                    "maxItems": 8,
                    "items": {
                      "type": "object",
                      "properties": {
                        "filename": {
                          "type": "string"
                        },
                        "content_base64": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "reference": {
                    "type": "string"
                  },
                  "carrier_nif": {
                    "type": "string"
                  },
                  "file": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Recibido y en pendientes"
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "415": {
            "description": "Tipo de archivo no admitido"
          },
          "422": {
            "description": "No se pudo leer"
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      }
    },
    "/carriers": {
      "get": {
        "summary": "(Clave de cargador) Tus transportistas y el estado de la conexión",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      }
    },
    "/connections": {
      "post": {
        "summary": "(Clave de cargador) Pedir la conexión a un transportista",
        "description": "Si está en PorteListo, le llega la solicitud. Si no, envía también email y le invitamos por email. Gratis para el cargador; el transportista necesita el plan Completo.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Si repites la petición con la misma clave, recibes la misma respuesta sin crear nada nuevo."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "carrier_nif"
                ],
                "properties": {
                  "carrier_nif": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Solicitud enviada (o ya conectados)"
          },
          "202": {
            "description": "Invitación enviada"
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "404": {
            "description": "No está en PorteListo y falta el email"
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      }
    },
    "/drivers": {
      "get": {
        "summary": "Conductores activos",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      }
    },
    "/vehicles": {
      "get": {
        "summary": "Camiones y remolques activos",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      }
    },
    "/worksites": {
      "get": {
        "summary": "v1.2 · Obras (clave de transportista)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Site"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      }
    },
    "/worksites/{id}": {
      "patch": {
        "summary": "v1.2 · Guardar la ubicación de una obra desde la telemática (clave de transportista, permiso write)",
        "description": "Por id o por código. Una ubicación puesta a mano en la oficina (source «manual») no se sobrescribe salvo con force: true (si no, 409 manual_location). Si el código existe con dos cargadores, 409 ambiguous: usa el id.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "location"
                ],
                "properties": {
                  "location": {
                    "type": "object",
                    "required": [
                      "lat",
                      "lon"
                    ],
                    "properties": {
                      "lat": {
                        "type": "number",
                        "minimum": -90,
                        "maximum": 90
                      },
                      "lon": {
                        "type": "number",
                        "minimum": -180,
                        "maximum": 180
                      },
                      "source": {
                        "type": "string",
                        "enum": [
                          "telematica",
                          "geocoder"
                        ],
                        "default": "telematica"
                      },
                      "radius_m": {
                        "type": "integer",
                        "minimum": 10,
                        "maximum": 5000,
                        "default": 150
                      }
                    }
                  },
                  "force": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Guardada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Site"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "404": {
            "description": "No existe"
          },
          "409": {
            "description": "manual_location o ambiguous",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Ubicación no válida"
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      }
    },
    "/cmrs": {
      "get": {
        "summary": "v1.3 · Lista de CMR (por fecha de carga; por defecto, 30 días)",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "emitido",
                "anulado"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 200
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Cmr"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      },
      "post": {
        "summary": "v1.3 · Crea un CMR listo para imprimir y firmar (plan Completo del transportista)",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CmrInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Creado",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Cmr"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "422": {
            "description": "Datos incompletos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      }
    },
    "/cmrs/{id}": {
      "get": {
        "summary": "v1.3 · Un CMR (por id o número)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Cmr"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "404": {
            "description": "No existe"
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      }
    },
    "/cmrs/{id}/cancel": {
      "post": {
        "summary": "v1.3 · Anula un CMR emitido",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reason"
                ],
                "properties": {
                  "reason": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Anulado"
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "404": {
            "description": "No existe"
          },
          "409": {
            "description": "No se puede anular"
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      }
    },
    "/load-sites": {
      "get": {
        "summary": "v1.2 · Lugares de carga activos (clave de transportista)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Site"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clave no válida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Sin permiso o plan sin API"
          },
          "429": {
            "description": "Más de 120 peticiones por minuto"
          }
        }
      }
    }
  }
}