{
  "openapi": "3.0.3",
  "info": {
    "title": "OnBox API",
    "version": "2.2.0",
    "description": "API de OnBox Servicios Logísticos para integraciones. Pedidos en las dos direcciones, stock, seguimiento y webhooks firmados.",
    "contact": {
      "email": "onboxlogistica@gmail.com"
    }
  },
  "servers": [
    {
      "url": "https://api.onboxservicioslogisticos.com",
      "description": "Producción"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Diagnóstico"
    },
    {
      "name": "Pedidos"
    },
    {
      "name": "Stock"
    },
    {
      "name": "Seguimiento"
    },
    {
      "name": "Webhooks"
    },
    {
      "name": "Entorno de prueba"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "obx_live_… / obx_test_…"
      }
    },
    "schemas": {
      "Pedido": {
        "type": "object",
        "description": "Un pedido tuyo dentro de nuestro sistema.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Identificador del pedido en nuestro sistema. Es el que usás en el resto de los endpoints."
          },
          "externalId": {
            "type": "string",
            "description": "El identificador que nos mandaste vos al crearlo. Sólo viene en el alta."
          },
          "internalId": {
            "type": "integer",
            "description": "Número interno. No lo uses como referencia: usá id."
          },
          "status": {
            "type": "string",
            "description": "Estado actual, en castellano (Pendiente, En preparación, Despachado, Entregado, Cancelado…). Texto para mostrar, no para lógica."
          },
          "statusCode": {
            "type": "string",
            "description": "Código ESTABLE del estado: pending, processing, shipped, in_transit, delivered, cancelled, returned, unknown. Integrá contra esto."
          },
          "amount": {
            "type": "number",
            "description": "Importe total declarado."
          },
          "buyer": {
            "type": "string",
            "description": "Nombre de quien recibe."
          },
          "platform": {
            "type": "string",
            "description": "De dónde entró el pedido."
          },
          "shippingType": {
            "type": "string",
            "description": "Modalidad de envío."
          },
          "tracking": {
            "type": "string",
            "description": "Número de seguimiento, cuando ya se asignó."
          },
          "orderDate": {
            "type": "string",
            "description": "Fecha del pedido (YYYY-MM-DD)."
          },
          "createdAt": {
            "type": "string",
            "description": "Alta, en ISO 8601 UTC."
          },
          "updatedAt": {
            "type": "string",
            "description": "Última modificación, en ISO 8601 UTC. Es el campo que usás para sincronizar."
          },
          "lines": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Sólo en el detalle: las líneas de producto."
          },
          "address": {
            "type": "string",
            "description": "Sólo en el detalle: dirección de entrega."
          },
          "city": {
            "type": "string",
            "description": "Sólo en el detalle: localidad."
          },
          "zip": {
            "type": "string",
            "description": "Sólo en el detalle: código postal."
          },
          "province": {
            "type": "string",
            "description": "Sólo en el detalle: provincia."
          }
        },
        "required": []
      },
      "Linea": {
        "type": "object",
        "description": "Una línea de producto de un pedido.",
        "properties": {
          "sku": {
            "type": "string",
            "description": "Código del producto."
          },
          "name": {
            "type": "string",
            "description": "Descripción."
          },
          "qty": {
            "type": "integer",
            "description": "Unidades."
          },
          "unitPrice": {
            "type": "number",
            "description": "Precio unitario."
          }
        },
        "required": []
      },
      "NuevoPedido": {
        "type": "object",
        "description": "Lo que nos mandás para crear un pedido.",
        "properties": {
          "externalId": {
            "type": "string",
            "description": "El identificador del pedido EN TU SISTEMA. Es la llave de idempotencia: repetirlo no duplica. Hasta 60 caracteres, letras, números, punto, guion y guion bajo."
          },
          "buyer.name": {
            "type": "string",
            "description": "Nombre de quien recibe."
          },
          "buyer.phone": {
            "type": "string",
            "description": "Teléfono de contacto. Sin esto el chofer no puede avisar."
          },
          "buyer.email": {
            "type": "string",
            "description": "Correo de contacto."
          },
          "shipping.address": {
            "type": "string",
            "description": "Calle y número."
          },
          "shipping.city": {
            "type": "string",
            "description": "Localidad."
          },
          "shipping.zip": {
            "type": "string",
            "description": "Código postal. Muy recomendado: es lo que usamos para resolver la zona y elegir el transportista (OCA / Andreani / cadete)."
          },
          "shipping.province": {
            "type": "string",
            "description": "Provincia. Ayuda a la asignación de zona cuando el CP es ambiguo."
          },
          "shipping.notes": {
            "type": "string",
            "description": "Indicaciones para la entrega (timbre, horario, referencia)."
          },
          "shipping.type": {
            "type": "string",
            "description": "Modalidad, si tenés una acordada con nosotros."
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Entre 1 y 200 items."
          },
          "items[].sku": {
            "type": "string",
            "description": "Tiene que existir en tu catálogo. Si uno no existe, se rechaza el pedido entero."
          },
          "items[].quantity": {
            "type": "integer",
            "description": "Entero mayor a cero."
          },
          "items[].unitPrice": {
            "type": "number",
            "description": "Precio unitario."
          },
          "items[].name": {
            "type": "string",
            "description": "Descripción, sólo informativa."
          },
          "amount": {
            "type": "number",
            "description": "Total del pedido. Si no lo mandás, lo calculamos con los items."
          },
          "paymentType": {
            "type": "string",
            "description": "Forma de pago, si aplica."
          },
          "notes": {
            "type": "string",
            "description": "Nota interna del pedido."
          },
          "rejectIfNoStock": {
            "type": "boolean",
            "description": "Por defecto false: el pedido entra igual y te avisamos en warnings. En true, lo rechazamos con 409."
          }
        },
        "required": [
          "externalId",
          "buyer.name",
          "shipping.address",
          "items",
          "items[].sku",
          "items[].quantity"
        ]
      },
      "Stock": {
        "type": "object",
        "description": "Disponibilidad por SKU.",
        "properties": {
          "sku": {
            "type": "string",
            "description": "Código del producto."
          },
          "name": {
            "type": "string",
            "description": "Descripción."
          },
          "available": {
            "type": "integer",
            "description": "Unidades disponibles para vender."
          }
        },
        "required": []
      },
      "Seguimiento": {
        "type": "object",
        "description": "Estado vivo de un envío.",
        "properties": {
          "reference": {
            "type": "string",
            "description": "Lo que consultaste."
          },
          "status": {
            "type": "string",
            "description": "Estado actual informado por el transportista (texto para mostrar)."
          },
          "statusCode": {
            "type": "string",
            "description": "Código estable del estado (mismo vocabulario que el pedido)."
          },
          "carrier": {
            "type": "string",
            "description": "Transportista (nombre): OCA, Andreani, cadete propio…"
          },
          "carrierTracking": {
            "type": "string",
            "description": "Número de seguimiento del transportista, si ya se asignó."
          },
          "eta": {
            "type": "string",
            "description": "Fecha de despacho / estimada, si la hay."
          },
          "timeline": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Los eventos en orden, del más viejo al más nuevo. Cada uno: { date, label }."
          }
        },
        "required": []
      },
      "Webhook": {
        "type": "object",
        "description": "Una URL tuya a la que te avisamos.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Identificador del webhook."
          },
          "url": {
            "type": "string",
            "description": "A dónde te avisamos. Tiene que ser https y un dominio público."
          },
          "events": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Qué eventos querés. [\"*\"] es todos."
          },
          "secret": {
            "type": "string",
            "description": "Sólo al crearlo y al rotarlo. Con esto validás la firma."
          },
          "active": {
            "type": "boolean",
            "description": "Si está recibiendo."
          },
          "consecutive_failures": {
            "type": "integer",
            "description": "Entregas seguidas que fallaron. Si crece, algo pasa de tu lado."
          }
        },
        "required": []
      }
    }
  },
  "paths": {
    "/v1/ping": {
      "get": {
        "tags": [
          "Diagnóstico"
        ],
        "summary": "Verificar la clave",
        "description": "Devuelve a qué cuenta pertenece tu clave, qué permisos tiene y cuánto te queda del límite. Es la primera llamada que conviene hacer.",
        "operationId": "get_v1_ping",
        "parameters": [],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "sellerId": "CLI-0042",
                  "mode": "live",
                  "scopes": [
                    "orders:read",
                    "orders:write"
                  ],
                  "rateLimit": {
                    "limit": 120,
                    "remaining": 119
                  },
                  "apiVersion": "2.2.0",
                  "time": "2026-08-18T13:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/logs": {
      "get": {
        "tags": [
          "Diagnóstico"
        ],
        "summary": "Ver tus últimas llamadas",
        "description": "Las últimas llamadas hechas con tu clave, con su X-Request-Id, el código HTTP y cuánto tardaron. Sirve para depurar sin escribirnos.",
        "operationId": "get_v1_logs",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "1–200. Por defecto 50.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": [
                    {
                      "request_id": "req_2ac903e5c69fb8db",
                      "method": "POST",
                      "path": "/v1/orders",
                      "status": 201,
                      "ms": 174,
                      "created_at": "2026-08-18T13:02:11.204Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders": {
      "get": {
        "tags": [
          "Pedidos"
        ],
        "summary": "Listar pedidos",
        "description": "Tus pedidos, paginados por cursor. Para sincronizar cada X minutos usá updatedSince en vez de barrer todo: te devuelve sólo lo que cambió.\n\nPermiso requerido: `orders:read`.",
        "operationId": "get_v1_orders",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "Desde (YYYY-MM-DD), por fecha de pedido.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Hasta (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filtrar por estado exacto, sin distinguir mayúsculas.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "description": "Sólo lo modificado después de ese momento (ISO 8601). Ordena por updatedAt.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "1–200. Por defecto 50.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Seguí desde acá. Viene en paging.nextCursor.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": [
                    {
                      "id": "SM-10045",
                      "internalId": 89696349,
                      "status": "Pendiente",
                      "statusCode": "pending",
                      "amount": 30800,
                      "buyer": "Juan Perez",
                      "platform": "API · Seller Manager",
                      "shippingType": null,
                      "tracking": null,
                      "orderDate": "2026-08-18",
                      "createdAt": "2026-08-18T13:02:11.204Z",
                      "updatedAt": "2026-08-18T13:02:11.400Z"
                    }
                  ],
                  "paging": {
                    "limit": 50,
                    "nextCursor": null
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Pedidos"
        ],
        "summary": "Crear un pedido",
        "description": "Nos mandás el pedido y queda cargado con el stock reservado. Es idempotente: reenviar el mismo externalId (o la misma Idempotency-Key) devuelve el pedido que ya existe con duplicate: true, sin duplicar nada.\n\nPermiso requerido: `orders:write`.",
        "operationId": "post_v1_orders",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Opcional pero recomendado. Un identificador único del intento; si el reintento llega con la misma, te devolvemos la misma respuesta.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "duplicate": false,
                  "data": {
                    "id": "SM-10045",
                    "internalId": 89696349,
                    "status": "Pendiente",
                    "statusCode": "pending",
                    "amount": 30800,
                    "buyer": "Juan Perez",
                    "platform": "API · Seller Manager",
                    "shippingType": null,
                    "tracking": null,
                    "orderDate": "2026-08-18",
                    "createdAt": "2026-08-18T13:02:11.204Z",
                    "updatedAt": "2026-08-18T13:02:11.400Z",
                    "externalId": "SM-10045",
                    "items": [
                      {
                        "sku": "ABC-123",
                        "name": null,
                        "quantity": 2,
                        "unitPrice": 15400
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "409": {
            "description": "No hay stock para todo lo pedido y mandaste rejectIfNoStock.",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "insufficient_stock",
                    "message": "No hay stock para todo lo pedido y mandaste rejectIfNoStock."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Uno o más SKU no existen en tu catálogo.",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unknown_sku",
                    "message": "Uno o más SKU no existen en tu catálogo."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "externalId": "SM-10045",
                "buyer": {
                  "name": "Juan Perez",
                  "phone": "1155667788"
                },
                "shipping": {
                  "address": "Av. Mitre 750",
                  "city": "Avellaneda",
                  "zip": "1870",
                  "province": "Buenos Aires"
                },
                "items": [
                  {
                    "sku": "ABC-123",
                    "quantity": 2,
                    "unitPrice": 15400
                  }
                ],
                "amount": 30800
              }
            }
          }
        }
      }
    },
    "/v1/orders/{id}": {
      "get": {
        "tags": [
          "Pedidos"
        ],
        "summary": "Detalle de un pedido",
        "description": "El pedido completo con sus líneas de producto, dirección y estado. Acepta tanto el id nuestro como el externalId que nos mandaste.\n\nPermiso requerido: `orders:read`.",
        "operationId": "get_v1_orders_id_",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": {
                    "id": "SM-10045",
                    "internalId": 89696349,
                    "status": "Pendiente",
                    "statusCode": "pending",
                    "amount": 30800,
                    "buyer": "Juan Perez",
                    "platform": "API · Seller Manager",
                    "shippingType": null,
                    "tracking": null,
                    "orderDate": "2026-08-18",
                    "createdAt": "2026-08-18T13:02:11.204Z",
                    "updatedAt": "2026-08-18T13:02:11.400Z",
                    "address": "Av. Mitre 750",
                    "city": "Avellaneda",
                    "zip": "1870",
                    "province": "Buenos Aires",
                    "lines": [
                      {
                        "sku": "ABC-123",
                        "name": "Producto de ejemplo",
                        "qty": 2,
                        "unitPrice": 15400
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders/{id}/cancel": {
      "post": {
        "tags": [
          "Pedidos"
        ],
        "summary": "Cancelar un pedido",
        "description": "Anula un pedido que todavía no salió del depósito y libera lo reservado. Si ya se preparó o despachó devuelve 409: eso lo resolvemos con una persona, no por API. Cancelar algo ya cancelado devuelve 200.\n\nPermiso requerido: `orders:write`.",
        "operationId": "post_v1_orders_id_cancel",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": {
                    "id": "SM-10045",
                    "internalId": 89696349,
                    "status": "Cancelado",
                    "statusCode": "pending",
                    "amount": 30800,
                    "buyer": "Juan Perez",
                    "platform": "API · Seller Manager",
                    "shippingType": null,
                    "tracking": null,
                    "orderDate": "2026-08-18",
                    "createdAt": "2026-08-18T13:02:11.204Z",
                    "updatedAt": "2026-08-18T13:02:11.400Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "404": {
            "description": "El pedido, envío o webhook no existe (o no es tuyo).",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "not_found",
                    "message": "El pedido, envío o webhook no existe (o no es tuyo)."
                  }
                }
              }
            }
          },
          "409": {
            "description": "El pedido ya salió de la etapa en la que se puede anular solo.",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "cannot_cancel",
                    "message": "El pedido ya salió de la etapa en la que se puede anular solo."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "reason": "El comprador se arrepintió"
              }
            }
          }
        }
      }
    },
    "/v1/stock": {
      "get": {
        "tags": [
          "Stock"
        ],
        "summary": "Stock disponible",
        "description": "Unidades disponibles por SKU. Es lo que se puede vender: ya tiene descontado lo reservado por pedidos abiertos.\n\nPermiso requerido: `stock:read`.",
        "operationId": "get_v1_stock",
        "parameters": [
          {
            "name": "sku",
            "in": "query",
            "description": "Un SKU puntual. Vacío devuelve todos.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "1–500. Por defecto 100.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": [
                    {
                      "sku": "ABC-123",
                      "name": "Producto de ejemplo",
                      "available": 42
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/tracking/{id}": {
      "get": {
        "tags": [
          "Seguimiento"
        ],
        "summary": "Estado de un envío",
        "description": "Estado vivo y línea de tiempo. Acepta el número de pedido o el de seguimiento del transportista.\n\nPermiso requerido: `tracking:read`.",
        "operationId": "get_v1_tracking_id_",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": {
                    "reference": "SM-10045",
                    "status": "En camino",
                    "statusCode": "in_transit",
                    "carrier": "OCA",
                    "carrierTracking": "OCA-123456",
                    "eta": "2026-08-20",
                    "timeline": [
                      {
                        "date": "2026-08-19T10:11:00-03:00",
                        "label": "Retirado del depósito"
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Listar tus webhooks",
        "description": "Tus URLs registradas, con el conteo de fallas seguidas de cada una.\n\nPermiso requerido: `webhooks:write`.",
        "operationId": "get_v1_webhooks",
        "parameters": [],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": [
                    {
                      "id": 7,
                      "url": "https://tu-sistema.com/hooks/onbox",
                      "events": [
                        "*"
                      ],
                      "active": true,
                      "consecutive_failures": 0
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Registrar un webhook",
        "description": "La URL tiene que ser https y de un dominio público. Te devolvemos el secret UNA sola vez: con ese secret validás la firma de cada aviso. El webhook queda escuchando desde ese momento; no reenviamos historia vieja.\n\nPermiso requerido: `webhooks:write`.",
        "operationId": "post_v1_webhooks",
        "parameters": [],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": {
                    "id": 7,
                    "url": "https://tu-sistema.com/hooks/onbox",
                    "events": [
                      "order.status_changed",
                      "order.delivered"
                    ],
                    "active": true,
                    "secret": "whsec_…"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Falta un campo o tiene un valor inválido.",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "validation_error",
                    "message": "Falta un campo o tiene un valor inválido."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "url": "https://tu-sistema.com/hooks/onbox",
                "events": [
                  "order.status_changed",
                  "order.delivered"
                ],
                "description": "Producción"
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/test": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Disparar una prueba",
        "description": "Te mandamos un evento ping firmado igual que los reales y te devolvemos qué contestó tu servidor. Sirve para verificar la firma antes de salir a producción.\n\nPermiso requerido: `webhooks:write`.",
        "operationId": "post_v1_webhooks_id_test",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": {
                    "deliveryId": 118,
                    "httpStatus": 200,
                    "error": null
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/rotate-secret": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Rotar el secret",
        "description": "Genera un secret nuevo. El anterior sigue siendo válido 24 horas para que puedas desplegar sin ventana de corte: durante ese lapso aceptá cualquiera de los dos.\n\nPermiso requerido: `webhooks:write`.",
        "operationId": "post_v1_webhooks_id_rotate_secret",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": {
                    "id": 7,
                    "secret": "whsec_…",
                    "anteriorValidoHasta": "2026-08-19T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/deliveries": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Historial de entregas",
        "description": "Los últimos 50 avisos que te mandamos, con el código que devolviste, los intentos y el error si lo hubo.\n\nPermiso requerido: `webhooks:write`.",
        "operationId": "get_v1_webhooks_id_deliveries",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": [
                    {
                      "id": 118,
                      "event": "order.status_changed",
                      "order_key": "SM-10045",
                      "status": "delivered",
                      "attempts": 1,
                      "response_status": 200,
                      "last_error": null
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/delete": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Dar de baja",
        "description": "Borra el webhook y su historial. También acepta DELETE sobre /v1/webhooks/{id}.\n\nPermiso requerido: `webhooks:write`.",
        "operationId": "post_v1_webhooks_id_delete",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "deleted": 7
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/sandbox/orders/{id}/advance": {
      "post": {
        "tags": [
          "Entorno de prueba"
        ],
        "summary": "Mover un pedido de prueba",
        "description": "Sólo con clave de prueba. Avanza un pedido simulado al estado que le pidas y dispara los webhooks de prueba como si fuera real. Es la forma de probar tu integración de punta a punta sin tocar producción.\n\nPermiso requerido: `orders:write`.",
        "operationId": "post_v1_sandbox_orders_id_advance",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": {
                    "id": "SM-10045",
                    "status": "Despachado",
                    "tracking": "OCA-123456"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "status": "Despachado",
                "tracking": "OCA-123456"
              }
            }
          }
        }
      }
    },
    "/dev/echo": {
      "get": {
        "tags": [
          "Entorno de prueba"
        ],
        "summary": "Receptor de prueba",
        "description": "Apuntá tu webhook a POST /dev/echo y después consultá acá para ver exactamente qué te mandamos: headers, firma y cuerpo, con la firma ya verificada de nuestro lado. Sirve para comparar contra tu implementación.\n\nPermiso requerido: `webhooks:write`.",
        "operationId": "get_dev_echo",
        "parameters": [],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": [
                    {
                      "delivery_id": 118,
                      "headers": {
                        "x-onbox-event": "ping"
                      },
                      "signature_ok": true
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Sin autenticar",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "unauthorized",
                    "message": "Falta la API key."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas llamadas",
            "content": {
              "application/json": {
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limited",
                    "message": "Superaste el límite."
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}