{
  "openapi": "3.0.3",
  "info": {
    "title": "Oktopus — COD commerce rail API (pago contra entrega LATAM)",
    "version": "1.0.0",
    "description": "API REST pública agent-facing de Oktopus: el riel de pago contra entrega (cash on delivery / COD) para LATAM. Crea órdenes COD, integra Dropi y cobra contra entrega. Reemplazo de Shopify + Releasit para dropshippers de Colombia, México y Perú. Para la operación completa (productos, landings, push a Dropi, precios) usar el MCP server en https://www.oktopus.lat/api/mcp.",
    "contact": {
      "name": "Oktopus",
      "url": "https://www.oktopus.lat/para-agentes",
      "email": "hola@oktopus.lat"
    }
  },
  "servers": [
    {
      "url": "https://www.oktopus.lat",
      "description": "Producción"
    }
  ],
  "externalDocs": {
    "description": "Para agentes (MCP, instalación)",
    "url": "https://www.oktopus.lat/para-agentes"
  },
  "tags": [
    {
      "name": "orders",
      "description": "Órdenes de pago contra entrega (COD orders)"
    },
    {
      "name": "geo",
      "description": "Datos geográficos LATAM (ciudades por país)"
    }
  ],
  "paths": {
    "/api/orders/create": {
      "post": {
        "tags": [
          "orders"
        ],
        "summary": "Crear una orden de pago contra entrega (COD order)",
        "description": "Crea una orden de pago contra entrega (cash on delivery). Dos modos: (1) landing_page_id — landing pública de Oktopus, sin credencial; (2) store_id + product_id — modo headless con una API key publishable (okto_pub_...) para el checkout embebible en sitios externos, validado contra los dominios permitidos de la key. Una key con environment=test crea la orden SIN empujarla a Dropi (sandbox). Aplica anti-fraude de precio server-side + anti-bot.",
        "security": [
          {
            "PublishableKey": []
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOrderInput"
              },
              "examples": {
                "headless": {
                  "summary": "Modo headless (store_id + product_id) con publishable key",
                  "value": {
                    "store_id": "11111111-1111-1111-1111-111111111111",
                    "product_id": "22222222-2222-2222-2222-222222222222",
                    "nombre": "Juan Pérez",
                    "telefono": "+57 300 1234567",
                    "departamento": "Cundinamarca",
                    "ciudad": "Bogotá",
                    "direccion": "Calle 123 #45-67",
                    "barrio": "Chapinero",
                    "pack": "1x",
                    "quantity": 1,
                    "unit_price": 120000,
                    "total_price": 120000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Orden creada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateOrderSuccess"
                }
              }
            }
          },
          "400": {
            "description": "Datos inválidos",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Credencial faltante o inválida (el modo headless requiere publishable key o sesión)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Origin/dominio no permitido para esta publishable key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit excedido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/geo/cities": {
      "get": {
        "tags": [
          "geo"
        ],
        "summary": "Ciudades / regiones por país (LATAM)",
        "description": "Devuelve el selector de departamentos/regiones → ciudades para un país. Cubre 8 países con dataset oficial (AR, CL, CO, EC, GT, MX, PE, PY). Público y cacheable (1 día).",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "required": true,
            "description": "Código ISO-3166 alpha-2 (ej. CO, MX, PE)",
            "schema": {
              "type": "string",
              "example": "CO"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Regiones y ciudades",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CitiesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Falta el parámetro country",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "PublishableKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "okto_pub_...",
        "description": "API key publishable de Oktopus (okto_live_pub_... / okto_test_pub_...) con scope orders:create. Generada en https://www.oktopus.lat/app/configuracion. La seguridad la da la validación de dominio server-side."
      }
    },
    "schemas": {
      "CreateOrderInput": {
        "type": "object",
        "description": "Debe enviar landing_page_id O (store_id + product_id).",
        "required": [
          "nombre",
          "telefono",
          "departamento",
          "ciudad",
          "direccion",
          "unit_price",
          "total_price"
        ],
        "properties": {
          "landing_page_id": {
            "type": "string",
            "format": "uuid",
            "description": "Modo 1: landing pública de Oktopus."
          },
          "store_id": {
            "type": "string",
            "format": "uuid",
            "description": "Modo 2: id de la tienda."
          },
          "product_id": {
            "type": "string",
            "format": "uuid",
            "description": "Modo 2: id del producto."
          },
          "nombre": {
            "type": "string",
            "minLength": 2,
            "maxLength": 100,
            "description": "Nombre del comprador."
          },
          "telefono": {
            "type": "string",
            "minLength": 6,
            "maxLength": 20,
            "description": "Teléfono del comprador."
          },
          "departamento": {
            "type": "string",
            "minLength": 2,
            "maxLength": 50,
            "description": "Departamento / región / estado."
          },
          "ciudad": {
            "type": "string",
            "minLength": 2,
            "maxLength": 100,
            "description": "Ciudad."
          },
          "direccion": {
            "type": "string",
            "minLength": 5,
            "maxLength": 300,
            "description": "Dirección de entrega."
          },
          "barrio": {
            "type": "string",
            "maxLength": 100
          },
          "referencia": {
            "type": "string",
            "maxLength": 300,
            "description": "Referencia de entrega."
          },
          "distrito": {
            "type": "string",
            "maxLength": 100,
            "description": "PE/CR/PA — 3er nivel administrativo."
          },
          "colonia": {
            "type": "string",
            "maxLength": 100,
            "description": "MX — sub-localidad."
          },
          "codigo_postal": {
            "type": "string",
            "maxLength": 15,
            "description": "MX/ES (req), AR (opt)."
          },
          "pack": {
            "type": "string",
            "default": "1x",
            "description": "Pack del producto (ej. 1x, 2x)."
          },
          "quantity": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10,
            "default": 1
          },
          "unit_price": {
            "type": "number",
            "description": "Precio unitario en la moneda del store."
          },
          "total_price": {
            "type": "number",
            "description": "Precio total (con piso anti-fraude server-side)."
          },
          "calle_secundaria": {
            "type": "string",
            "maxLength": 120,
            "description": "EC — segunda calle (intersección); se anexa a la dirección para el courier."
          },
          "color_casa": {
            "type": "string",
            "maxLength": 120,
            "description": "EC — color/descripción de la casa; se anexa a la dirección para el courier."
          },
          "variant_id": {
            "type": "integer",
            "description": "ID de la variante elegida (productos con talla/color). Se valida server-side contra las variantes del producto."
          },
          "variant_title": {
            "type": "string",
            "maxLength": 150,
            "description": "Título display de la variante; el server lo recalcula de la variante real."
          }
        }
      },
      "CreateOrderSuccess": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "order_number": {
                "type": "integer"
              },
              "status": {
                "type": "string",
                "example": "pending"
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "CitiesResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "type": "object",
            "properties": {
              "country": {
                "type": "string",
                "example": "CO"
              },
              "official": {
                "type": "boolean",
                "description": "true si hay dataset oficial; false → el cliente usa texto libre."
              },
              "regions": {
                "type": "object",
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "description": "Mapa departamento/región → [ciudades]."
              }
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "error": {
            "type": "string"
          },
          "details": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  }
}
