{
  "openapi": "3.1.0",
  "info": {
    "title": "API Tracking Perú",
    "version": "1",
    "summary": "Rastreo, creación de guías y catálogo de agencias de 5 couriers peruanos detrás de un solo contrato REST.",
    "description": "Un contrato para Expreso Marvisur, Olva Courier, Urbano Express, Cruz del Sur Cargo, Shalom.\n\nLos estados llegan normalizados a un vocabulario canónico de once valores, con el literal original del courier adjunto en `status_raw`. Lo que no reconocemos llega como `unknown` con su literal, nunca aproximado al más parecido.\n\nQué publica cada courier y con cuánta evidencia está verificado su mapeo lo dice `GET /v1/carriers`.\n\nEste documento se genera en cada despliegue desde la misma fuente que la referencia publicada en https://tracking-peru.com/docs. Los esquemas se derivan de ejemplos reales recortados: los campos que aparecen existen y su tipo es el que dice, pero la lista no es exhaustiva y por eso no se declara `required`.",
    "contact": {
      "email": "hola@tracking-peru.com",
      "url": "https://tracking-peru.com/docs"
    },
    "termsOfService": "https://tracking-peru.com/terms"
  },
  "servers": [
    {
      "url": "https://api.tracking-peru.com",
      "description": "Producción"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/v1/shipments": {
      "post": {
        "operationId": "shipments",
        "summary": "Crea una guía con el courier. Un cuerpo canónico para todos.",
        "description": "El mismo cuerpo sirve para todos los couriers. Lo que sólo pide uno va en carrier_options, y su esquema se publica en GET /v1/carriers.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "carrier": {
                    "type": "string"
                  },
                  "service": {
                    "type": "object",
                    "properties": {
                      "delivery": {
                        "type": "string"
                      }
                    },
                    "additionalProperties": true
                  },
                  "origin": {
                    "type": "object",
                    "properties": {
                      "agency_id": {
                        "type": "string"
                      }
                    },
                    "additionalProperties": true
                  },
                  "destination": {
                    "type": "object",
                    "properties": {
                      "agency_id": {
                        "type": "string"
                      }
                    },
                    "additionalProperties": true
                  },
                  "sender": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "document": {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string"
                          },
                          "number": {
                            "type": "string"
                          }
                        },
                        "additionalProperties": true
                      },
                      "phone": {
                        "type": "string"
                      }
                    },
                    "additionalProperties": true
                  },
                  "receiver": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "document": {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string"
                          },
                          "number": {
                            "type": "string"
                          }
                        },
                        "additionalProperties": true
                      },
                      "phone": {
                        "type": "string"
                      }
                    },
                    "additionalProperties": true
                  },
                  "parcels": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "weight_kg": {
                          "type": "number"
                        },
                        "content": {
                          "type": "object",
                          "properties": {
                            "category": {
                              "type": "string"
                            }
                          },
                          "additionalProperties": true
                        },
                        "declared_value": {
                          "type": "integer"
                        }
                      },
                      "additionalProperties": true
                    }
                  },
                  "payer": {
                    "type": "string"
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "carrier": "olva",
                "service": {
                  "delivery": "agency"
                },
                "origin": {
                  "agency_id": "hq:12"
                },
                "destination": {
                  "agency_id": "161"
                },
                "sender": {
                  "name": "Tu Tienda SAC",
                  "document": {
                    "type": "ruc",
                    "number": "20xxxxxxxxx"
                  },
                  "phone": "999999999"
                },
                "receiver": {
                  "name": "Ana Quispe",
                  "document": {
                    "type": "dni",
                    "number": "0xxxxxxx"
                  },
                  "phone": "988888888"
                },
                "parcels": [
                  {
                    "weight_kg": 1.2,
                    "content": {
                      "category": "ropa"
                    },
                    "declared_value": 120
                  }
                ],
                "payer": "receiver"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "201 Created · application/json",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "carrier": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "tracking": {
                      "type": "object",
                      "properties": {
                        "number": {
                          "type": "string"
                        }
                      },
                      "additionalProperties": true
                    },
                    "parcels": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "index": {
                            "type": "integer"
                          },
                          "tracking_number": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          }
                        },
                        "additionalProperties": true
                      }
                    },
                    "payment_instructions": {
                      "type": "string"
                    }
                  },
                  "additionalProperties": true
                },
                "example": {
                  "id": "shp_01K...",
                  "carrier": "olva",
                  "status": "pending_payment",
                  "tracking": {
                    "number": "2431838-26"
                  },
                  "parcels": [
                    {
                      "index": 0,
                      "tracking_number": "2431838-26",
                      "url": "…/label"
                    }
                  ],
                  "payment_instructions": "Se paga en destino al recoger."
                }
              }
            }
          },
          "4XX": {
            "description": "Error. El cuerpo sigue siempre la misma forma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/shipments/quote": {
      "post": {
        "operationId": "quote",
        "summary": "Cuánto cuesta antes de crearla. Sin efectos.",
        "description": "Cotizar no crea nada ni consume cupo de órdenes.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "carrier": {
                    "type": "string"
                  },
                  "service": {
                    "type": "object",
                    "properties": {
                      "delivery": {
                        "type": "string"
                      }
                    },
                    "additionalProperties": true
                  },
                  "origin": {
                    "type": "object",
                    "properties": {
                      "ubigeo": {
                        "type": "string"
                      }
                    },
                    "additionalProperties": true
                  },
                  "destination": {
                    "type": "object",
                    "properties": {
                      "ubigeo": {
                        "type": "string"
                      }
                    },
                    "additionalProperties": true
                  },
                  "parcels": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "weight_kg": {
                          "type": "integer"
                        }
                      },
                      "additionalProperties": true
                    }
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "carrier": "olva",
                "service": {
                  "delivery": "agency"
                },
                "origin": {
                  "ubigeo": "150101"
                },
                "destination": {
                  "ubigeo": "110101"
                },
                "parcels": [
                  {
                    "weight_kg": 1
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "200 OK · application/json",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "carrier": {
                      "type": "string"
                    },
                    "amount": {
                      "type": "integer"
                    },
                    "currency": {
                      "type": "string"
                    },
                    "delivery": {
                      "type": "string"
                    }
                  },
                  "additionalProperties": true
                },
                "example": {
                  "carrier": "olva",
                  "amount": 8,
                  "currency": "PEN",
                  "delivery": "agency"
                }
              }
            }
          },
          "4XX": {
            "description": "Error. El cuerpo sigue siempre la misma forma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/carriers": {
      "get": {
        "operationId": "carriers",
        "summary": "Qué carriers hay, qué puede cada uno y cuánta evidencia lo respalda.",
        "description": "Es el endpoint de descubrimiento: tu código puede leer el estado en vez de confiar en esta página.",
        "responses": {
          "200": {
            "description": "200 OK · application/json",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "carriers": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "enabled": {
                            "type": "boolean"
                          },
                          "verified": {
                            "type": "object",
                            "properties": {
                              "level": {
                                "type": "string"
                              },
                              "last_probe": {
                                "type": "string"
                              },
                              "notes": {
                                "type": "string"
                              }
                            },
                            "additionalProperties": true
                          },
                          "id_format": {
                            "type": "string"
                          },
                          "examples": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "detection": {
                            "type": "string"
                          },
                          "requires_code": {
                            "type": "boolean"
                          },
                          "requires_end_user_auth": {
                            "type": "boolean"
                          },
                          "anonymous_timeline": {
                            "type": "boolean"
                          },
                          "provides": {
                            "type": "object",
                            "properties": {
                              "parties": {
                                "type": "boolean"
                              },
                              "event_location": {
                                "type": "boolean"
                              },
                              "packages": {
                                "type": "boolean"
                              },
                              "payment_status": {
                                "type": "boolean"
                              }
                            },
                            "additionalProperties": true
                          }
                        },
                        "additionalProperties": true
                      }
                    }
                  },
                  "additionalProperties": true
                },
                "example": {
                  "carriers": [
                    {
                      "id": "marvisur",
                      "name": "Expreso Marvisur",
                      "enabled": true,
                      "verified": {
                        "level": "live",
                        "last_probe": "2026-08-03T22:10:23Z",
                        "notes": "hit y miss reproducidos byte a byte contra el upstream"
                      },
                      "id_format": "V###-####### (serie V + 3 dígitos, distinto de V000)",
                      "examples": [
                        "V999-9999999"
                      ],
                      "detection": "strong",
                      "requires_code": false,
                      "requires_end_user_auth": false,
                      "anonymous_timeline": true,
                      "provides": {
                        "parties": true,
                        "event_location": true,
                        "packages": true,
                        "payment_status": true
                      }
                    }
                  ]
                }
              }
            }
          },
          "4XX": {
            "description": "Error. El cuerpo sigue siempre la misma forma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/tracking": {
      "get": {
        "operationId": "tracking",
        "summary": "Rastreo unificado por número de guía.",
        "description": "Cada evento trae los tres: status (canónico), status_raw (el literal del courier) y carrier_code (su código, si hay) — normalizar no significa perder el dato original. Y para saber si un envío TERMINÓ usa los bools terminal/delivered, no el nombre: available_for_pickup no es entrega, y una devolución también es terminal. Segundo factor: algunos carriers piden además el código de orden — pásalo como &code=<código>. Es OBLIGATORIO en los que declaran requires_code en GET /v1/carriers (hoy Shalom: sin él responde 422) y se ignora en el resto.",
        "parameters": [
          {
            "name": "number",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Número de guía."
          },
          {
            "name": "carrier",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Id del courier. Obligatorio si su formato no es distinguible."
          },
          {
            "name": "code",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Código de orden. Sólo los carriers con requires_code lo exigen."
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Con `raw`, adjunta la respuesta cruda del courier."
          }
        ],
        "responses": {
          "200": {
            "description": "200 OK · application/json",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "carrier": {
                      "type": "string"
                    },
                    "tracking_number": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "status_raw": {
                      "type": "string"
                    },
                    "delivered": {
                      "type": "boolean"
                    },
                    "terminal": {
                      "type": "boolean"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "seq": {
                            "type": "integer"
                          },
                          "status": {
                            "type": "string"
                          },
                          "status_raw": {
                            "type": "string"
                          },
                          "carrier_code": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          },
                          "occurred_at": {
                            "type": "string"
                          },
                          "time_precision": {
                            "type": "string"
                          },
                          "location": {
                            "type": "object",
                            "properties": {
                              "name": {
                                "type": "string"
                              },
                              "structured": {
                                "type": "boolean"
                              }
                            },
                            "additionalProperties": true
                          }
                        },
                        "additionalProperties": true
                      }
                    },
                    "detail": {
                      "type": "string"
                    },
                    "fetched_at": {
                      "type": "string"
                    }
                  },
                  "additionalProperties": true
                },
                "example": {
                  "carrier": "marvisur",
                  "tracking_number": "V001-0000001",
                  "status": "delivered",
                  "status_raw": "ENTREGADO",
                  "delivered": true,
                  "terminal": true,
                  "events": [
                    {
                      "seq": 0,
                      "status": "registered",
                      "status_raw": "RECEPCION",
                      "carrier_code": "0",
                      "description": "SU ENVÍO FUÉ RECEPCIONADO EN NUESTRA SEDE",
                      "occurred_at": "2023-07-01T07:54:06-05:00",
                      "time_precision": "second",
                      "location": {
                        "name": "GARCI CARBAJAL",
                        "structured": false
                      }
                    }
                  ],
                  "detail": "full",
                  "fetched_at": "2026-08-03T22:10:31Z"
                }
              }
            }
          },
          "4XX": {
            "description": "Error. El cuerpo sigue siempre la misma forma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/tracking/{carrier}/{number}": {
      "get": {
        "operationId": "trackingRoute",
        "summary": "La misma consulta con el carrier en la ruta.",
        "description": "Idéntica respuesta. Existe porque un carrier explícito en la ruta se cachea y se loguea mejor que un query param.",
        "parameters": [
          {
            "name": "carrier",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Id del courier."
          },
          {
            "name": "number",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Identificador en la ruta."
          }
        ],
        "responses": {
          "200": {
            "description": "200 OK · /v1/tracking/marvisur/V001-0000001",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "carrier": {
                      "type": "string"
                    },
                    "tracking_number": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "status_raw": {
                      "type": "string"
                    },
                    "delivered": {
                      "type": "boolean"
                    },
                    "terminal": {
                      "type": "boolean"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "seq": {
                            "type": "integer"
                          },
                          "status": {
                            "type": "string"
                          },
                          "status_raw": {
                            "type": "string"
                          },
                          "carrier_code": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          },
                          "occurred_at": {
                            "type": "string"
                          },
                          "time_precision": {
                            "type": "string"
                          },
                          "location": {
                            "type": "object",
                            "properties": {
                              "name": {
                                "type": "string"
                              },
                              "structured": {
                                "type": "boolean"
                              }
                            },
                            "additionalProperties": true
                          }
                        },
                        "additionalProperties": true
                      }
                    },
                    "detail": {
                      "type": "string"
                    },
                    "fetched_at": {
                      "type": "string"
                    }
                  },
                  "additionalProperties": true
                },
                "example": {
                  "carrier": "marvisur",
                  "tracking_number": "V001-0000001",
                  "status": "delivered",
                  "status_raw": "ENTREGADO",
                  "delivered": true,
                  "terminal": true,
                  "events": [
                    {
                      "seq": 0,
                      "status": "registered",
                      "status_raw": "RECEPCION",
                      "carrier_code": "0",
                      "description": "SU ENVÍO FUÉ RECEPCIONADO EN NUESTRA SEDE",
                      "occurred_at": "2023-07-01T07:54:06-05:00",
                      "time_precision": "second",
                      "location": {
                        "name": "GARCI CARBAJAL",
                        "structured": false
                      }
                    }
                  ],
                  "detail": "full",
                  "fetched_at": "2026-08-03T22:10:31Z"
                }
              }
            }
          },
          "4XX": {
            "description": "Error. El cuerpo sigue siempre la misma forma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/tracking/batch": {
      "post": {
        "operationId": "batch",
        "summary": "Hasta 50 envíos por request, con error por ítem.",
        "description": "Responde 200 aunque haya ítems fallidos: el error va por ítem. Aquí no hay detección automática.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "custom_id": {
                          "type": "string"
                        },
                        "carrier": {
                          "type": "string"
                        },
                        "number": {
                          "type": "string"
                        }
                      },
                      "additionalProperties": true
                    }
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "items": [
                  {
                    "custom_id": "a",
                    "carrier": "marvisur",
                    "number": "V001-0000001"
                  },
                  {
                    "custom_id": "b",
                    "carrier": "marvisur",
                    "number": "V999-9999999"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "200 OK · application/json",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "custom_id": {
                            "type": "string"
                          },
                          "carrier": {
                            "type": "string"
                          },
                          "number": {
                            "type": "string"
                          },
                          "ok": {
                            "type": "boolean"
                          },
                          "shipment": {
                            "type": "object",
                            "properties": {
                              "…": {
                                "type": "string"
                              }
                            },
                            "additionalProperties": true
                          }
                        },
                        "additionalProperties": true
                      }
                    },
                    "summary": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "ok": {
                          "type": "integer"
                        },
                        "failed": {
                          "type": "integer"
                        }
                      },
                      "additionalProperties": true
                    }
                  },
                  "additionalProperties": true
                },
                "example": {
                  "results": [
                    {
                      "custom_id": "a",
                      "carrier": "marvisur",
                      "number": "V001-0000001",
                      "ok": true,
                      "shipment": {
                        "…": "el Shipment completo"
                      }
                    },
                    {
                      "custom_id": "b",
                      "carrier": "marvisur",
                      "number": "V999-9999999",
                      "ok": false,
                      "error": {
                        "code": "not_found",
                        "message": "no se encontró el envío"
                      }
                    }
                  ],
                  "summary": {
                    "total": 2,
                    "ok": 1,
                    "failed": 1
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "Error. El cuerpo sigue siempre la misma forma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/agencies": {
      "get": {
        "operationId": "agencies",
        "summary": "Catálogo de agencias con filtros y paginación.",
        "description": "ubigeo_source es lo que hace auditable el join: \"carrier\" lo dio el courier, \"matched\" lo resolvimos por texto y puede fallar.",
        "parameters": [
          {
            "name": "carrier",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filtra por courier."
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Página, desde 1."
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Resultados por página. El máximo es 500."
          }
        ],
        "responses": {
          "200": {
            "description": "200 OK · ?carrier=olva&ubigeo=150122",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agencies": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "carrier": {
                            "type": "string"
                          },
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "location": {
                            "type": "object",
                            "properties": {
                              "address": {
                                "type": "string"
                              },
                              "department": {
                                "type": "string"
                              },
                              "province": {
                                "type": "string"
                              },
                              "district": {
                                "type": "string"
                              },
                              "ubigeo": {
                                "type": "string"
                              },
                              "structured": {
                                "type": "boolean"
                              }
                            },
                            "additionalProperties": true
                          },
                          "ubigeo_level": {
                            "type": "string"
                          },
                          "ubigeo_source": {
                            "type": "string"
                          },
                          "geo": {
                            "type": "object",
                            "properties": {
                              "lat": {
                                "type": "number"
                              },
                              "lng": {
                                "type": "number"
                              }
                            },
                            "additionalProperties": true
                          },
                          "hours": {
                            "type": "object",
                            "properties": {
                              "weekly": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "weekday": {
                                      "type": "integer"
                                    },
                                    "spans": {
                                      "type": "array",
                                      "items": {
                                        "type": "object",
                                        "properties": {
                                          "open": {
                                            "type": "string"
                                          },
                                          "close": {
                                            "type": "string"
                                          }
                                        },
                                        "additionalProperties": true
                                      }
                                    }
                                  },
                                  "additionalProperties": true
                                }
                              },
                              "structured": {
                                "type": "boolean"
                              },
                              "time_zone": {
                                "type": "string"
                              }
                            },
                            "additionalProperties": true
                          },
                          "kind": {
                            "type": "string"
                          },
                          "services": {
                            "type": "object",
                            "properties": {
                              "dropoff": {
                                "type": "boolean"
                              },
                              "pickup": {
                                "type": "boolean"
                              }
                            },
                            "additionalProperties": true
                          },
                          "synced_at": {
                            "type": "string"
                          }
                        },
                        "additionalProperties": true
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "page": {
                          "type": "integer"
                        },
                        "per_page": {
                          "type": "integer"
                        },
                        "total": {
                          "type": "integer"
                        },
                        "total_pages": {
                          "type": "integer"
                        }
                      },
                      "additionalProperties": true
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "sources": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "additionalProperties": true
                    }
                  },
                  "additionalProperties": true
                },
                "example": {
                  "agencies": [
                    {
                      "carrier": "olva",
                      "id": "948",
                      "name": "AGENTE OLVA MIRAFLORES - BENAVIDES C18",
                      "location": {
                        "address": "AV ALFREDO BENAVIDES NRO 1851",
                        "department": "LIMA",
                        "province": "LIMA",
                        "district": "MIRAFLORES",
                        "ubigeo": "150122",
                        "structured": true
                      },
                      "ubigeo_level": "district",
                      "ubigeo_source": "carrier",
                      "geo": {
                        "lat": -12.1265813,
                        "lng": -77.013244
                      },
                      "hours": {
                        "weekly": [
                          {
                            "weekday": 1,
                            "spans": [
                              {
                                "open": "08:00",
                                "close": "20:00"
                              }
                            ]
                          }
                        ],
                        "structured": true,
                        "time_zone": "America/Lima"
                      },
                      "kind": "agent",
                      "services": {
                        "dropoff": true,
                        "pickup": true
                      },
                      "synced_at": "2026-08-03T22:10:19Z"
                    }
                  ],
                  "pagination": {
                    "page": 1,
                    "per_page": 20,
                    "total": 417,
                    "total_pages": 21
                  },
                  "meta": {
                    "sources": [
                      "…frescura por carrier"
                    ]
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "Error. El cuerpo sigue siempre la misma forma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/coverage": {
      "get": {
        "operationId": "coverage",
        "summary": "Qué carriers tienen agencia en un distrito. Distingue \"no cubre\" de \"no sabemos\".",
        "description": "Solo un courier que publica distrito puede recibir un \"no cubre\". El que da su ubicación como texto libre cae en \"unknown\", con el motivo escrito.",
        "parameters": [
          {
            "name": "ubigeo",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Ubigeo INEI. Alternativa a `department` o `near`."
          },
          {
            "name": "department",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Departamento. Se puede afinar con `province` y `district`."
          },
          {
            "name": "province",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Provincia, junto con `department`."
          },
          {
            "name": "district",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Distrito, junto con `department` y `province`."
          },
          {
            "name": "near",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Coordenadas `lat,lng`. Alternativa a `ubigeo` o `department`."
          }
        ],
        "responses": {
          "200": {
            "description": "200 OK · ?ubigeo=150101"
          },
          "4XX": {
            "description": "Error. El cuerpo sigue siempre la misma forma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/agencies/{carrier}/{id}": {
      "get": {
        "operationId": "agencyDetail",
        "summary": "Detalle de una agencia.",
        "description": "Devuelve la Agency pelada, sin envoltorio. La PK natural es el par (carrier, id).",
        "parameters": [
          {
            "name": "carrier",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Id del courier."
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Identificador en la ruta."
          }
        ],
        "responses": {
          "200": {
            "description": "200 OK · /v1/agencies/olva/579",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "carrier": {
                      "type": "string"
                    },
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "location": {
                      "type": "object",
                      "properties": {
                        "address": {
                          "type": "string"
                        },
                        "department": {
                          "type": "string"
                        },
                        "province": {
                          "type": "string"
                        },
                        "district": {
                          "type": "string"
                        },
                        "ubigeo": {
                          "type": "string"
                        },
                        "structured": {
                          "type": "boolean"
                        }
                      },
                      "additionalProperties": true
                    },
                    "ubigeo_level": {
                      "type": "string"
                    },
                    "ubigeo_source": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string"
                    },
                    "services": {
                      "type": "object",
                      "properties": {
                        "dropoff": {
                          "type": "boolean"
                        },
                        "pickup": {
                          "type": "boolean"
                        }
                      },
                      "additionalProperties": true
                    },
                    "synced_at": {
                      "type": "string"
                    }
                  },
                  "additionalProperties": true
                },
                "example": {
                  "carrier": "olva",
                  "id": "579",
                  "name": "TIENDA CHACHAPOYAS",
                  "location": {
                    "address": "JR. AMAZONAS 1120",
                    "department": "AMAZONAS",
                    "province": "CHACHAPOYAS",
                    "district": "CHACHAPOYAS",
                    "ubigeo": "010101",
                    "structured": true
                  },
                  "ubigeo_level": "district",
                  "ubigeo_source": "carrier",
                  "kind": "office",
                  "services": {
                    "dropoff": true,
                    "pickup": true
                  },
                  "synced_at": "2026-08-03T22:10:19Z"
                }
              }
            }
          },
          "4XX": {
            "description": "Error. El cuerpo sigue siempre la misma forma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks": {
      "post": {
        "operationId": "webhooks",
        "summary": "Registra tu endpoint. Devuelve el signing secret una sola vez.",
        "description": "Se guarda deshabilitado y recibe un ping firmado: solo se habilita si devuelves el challenge como cuerpo.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string"
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "url": "https://tuservicio.com/hooks/tracking"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "201 Created · application/json",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhook": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "url": {
                          "type": "string"
                        },
                        "enabled": {
                          "type": "boolean"
                        },
                        "created_at": {
                          "type": "string"
                        }
                      },
                      "additionalProperties": true
                    },
                    "signing_secret": {
                      "type": "string"
                    },
                    "verified": {
                      "type": "boolean"
                    },
                    "usage": {
                      "type": "object",
                      "properties": {
                        "active_subscriptions": {
                          "type": "integer"
                        },
                        "max_subscriptions": {
                          "type": "integer"
                        }
                      },
                      "additionalProperties": true
                    }
                  },
                  "additionalProperties": true
                },
                "example": {
                  "webhook": {
                    "id": 11,
                    "url": "https://tuservicio.com/hooks/tracking",
                    "enabled": true,
                    "created_at": "2026-08-04T10:00:00Z"
                  },
                  "signing_secret": "9f2b…64 hex…c1",
                  "verified": true,
                  "usage": {
                    "active_subscriptions": 0,
                    "max_subscriptions": 50
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "Error. El cuerpo sigue siempre la misma forma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/tracking/subscriptions": {
      "post": {
        "operationId": "subscriptions",
        "summary": "Suscribe un envío: te avisamos cuando cambie de estado.",
        "description": "carrier es OBLIGATORIO: no hay detección automática. Un rastreo mal detectado se ve en la respuesta; una suscripción mal detectada no la mira nadie durante semanas. El code es el 2º factor (código de orden): obligatorio para los carriers con requires_code como Shalom, opcional para el resto.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "carrier": {
                    "type": "string"
                  },
                  "number": {
                    "type": "string"
                  },
                  "code": {
                    "type": "string"
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "carrier": "marvisur",
                "number": "V001-0000001",
                "code": "2º factor — requerido si el carrier declara requires_code"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "201 Created · application/json",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subscription": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "carrier": {
                          "type": "string"
                        },
                        "tracking_number": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string"
                        },
                        "next_poll_at": {
                          "type": "string"
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "expires_at": {
                          "type": "string"
                        }
                      },
                      "additionalProperties": true
                    },
                    "outcome": {
                      "type": "string"
                    },
                    "usage": {
                      "type": "object",
                      "properties": {
                        "active_subscriptions": {
                          "type": "integer"
                        },
                        "max_subscriptions": {
                          "type": "integer"
                        }
                      },
                      "additionalProperties": true
                    }
                  },
                  "additionalProperties": true
                },
                "example": {
                  "subscription": {
                    "id": 4821,
                    "carrier": "marvisur",
                    "tracking_number": "V001-0000001",
                    "status": "active",
                    "next_poll_at": "2026-08-04T10:16:00Z",
                    "created_at": "2026-08-04T10:00:00Z",
                    "expires_at": "2026-08-25T10:00:00Z"
                  },
                  "outcome": "created",
                  "usage": {
                    "active_subscriptions": 8,
                    "max_subscriptions": 50
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "Error. El cuerpo sigue siempre la misma forma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "La key va del lado del servidor. Se obtiene gratis en https://tracking-peru.com/#get-started y se puede rotar desde el panel."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Todos los errores del API tienen esta forma.",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Código estable. No cambia aunque cambie el mensaje.",
                "enum": [
                  "bad_request",
                  "invalid_tracking_number",
                  "carrier_unknown",
                  "carrier_ambiguous",
                  "unauthorized",
                  "key_expired",
                  "carrier_auth_failed",
                  "forbidden",
                  "not_found",
                  "conflict",
                  "webhook_not_configured",
                  "payload_too_large",
                  "unsupported_media_type",
                  "carrier_rejected",
                  "rate_limited",
                  "quota_exceeded",
                  "carrier_rate_limited",
                  "internal",
                  "carrier_not_supported",
                  "carrier_unavailable",
                  "carrier_disabled",
                  "carrier_cooldown",
                  "carrier_timeout"
                ]
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string",
                "description": "Identificador de la petición, para reportar un problema."
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      }
    }
  }
}