{
  "openapi": "3.1.0",
  "info": {
    "title": "BoolPay Public Web API",
    "version": "1.0.0",
    "summary": "Endpoints públicos del sitio boolpay.mx",
    "description": "API pública del sitio web de BoolPay. Actualmente expone el endpoint de contacto usado por los formularios del sitio (demo, ventas, onboarding de comercio y programa de distribuidores).\n\nLa API de procesamiento de pagos de BoolPay es privada y requiere credenciales: solicita acceso en https://boolpay.mx/contacto o escribe a ventas@boolpay.mx.\n\nTodos los errores se devuelven como JSON con la forma `{ \"ok\": false, \"error\": { \"code\", \"message\", \"hint\", \"status\" }, \"docs\" }`. El campo `error.code` es estable y seguro para lógica automatizada; `error.hint` describe cómo resolverlo.\n\n## Versionado\n\nLa versión vive en el path: `/api/v1/…`. Versión vigente: **v1**. Toda respuesta\nincluye el header `API-Version`. Una versión estable nunca recibe cambios\nincompatibles; agregar campos opcionales a una respuesta no se considera\nincompatible. La deprecación se anuncia con al menos **180 días** de aviso\nmediante `Deprecation: true` (RFC 9745) y `Sunset: <http-date>` (RFC 8594), más\nun header `Link: rel=\"successor-version\"`. Pasada la fecha de sunset la versión\nresponde `410 Gone` con `error.code = \"api_version_sunset\"`.\nPolítica legible por máquinas: https://boolpay.mx/api/versioning\n\n## Rate limits\n\n`/api/*`: 60 peticiones por 60 segundos por IP.\nEndpoints de contacto: 1 petición por 30 segundos por IP.\nToda respuesta incluye `RateLimit`, `RateLimit-Policy` y `X-RateLimit-*`; los 429\nincluyen `Retry-After` en segundos. No hace falta provocar un 429 para descubrir\nel límite.",
    "contact": {
      "name": "BoolPay — Comercial",
      "email": "ventas@boolpay.mx",
      "url": "https://boolpay.mx/contacto"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://boolpay.mx/terminos.html"
    },
    "termsOfService": "https://boolpay.mx/terminos.html"
  },
  "servers": [
    {
      "url": "https://boolpay.mx/api/v1",
      "description": "Producción — v1 (vigente)"
    },
    {
      "url": "https://boolpay.mx",
      "description": "Producción — raíz del sitio (endpoints legacy)"
    }
  ],
  "tags": [
    {
      "name": "discovery",
      "description": "Descubrimiento de la API, versiones y estado"
    },
    {
      "name": "contact",
      "description": "Formularios de contacto del sitio público"
    }
  ],
  "paths": {
    "/contact.php": {
      "post": {
        "tags": [
          "contact"
        ],
        "operationId": "submitContactForm",
        "summary": "Enviar un formulario de contacto (alias legacy de /api/v1/contact)",
        "description": "Recibe una solicitud de demo, un mensaje a ventas, un onboarding de comercio o una aplicación al programa de distribuidores, y la entrega por correo al equipo comercial.\n\nProtección antispam activa: honeypot (`_bp_check`), ventana temporal (`ts`), captcha visual (`captcha_expected` / `captcha_input`) y rate limit de 30 s por IP. Las integraciones automatizadas no pueden resolver el captcha: solicita acceso a la API privada en ventas@boolpay.mx.\n\n**Alias vigente:** `POST /api/v1/contact`. Ambas rutas comparten implementación y contrato; la ruta versionada es la recomendada para integraciones nuevas.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactRequest"
              },
              "examples": {
                "ventas": {
                  "summary": "Mensaje a ventas",
                  "value": {
                    "form_type": "ventas",
                    "ts": 1756800000000,
                    "nombre": "Ana Rivera",
                    "empresa": "Comercializadora Norte",
                    "email": "ana@empresa.mx",
                    "telefono": "+52 55 1234 5678",
                    "tema": "Propuesta comercial",
                    "mensaje": "Operamos 12 sucursales y buscamos terminales POS.",
                    "captcha_expected": "K7QMR",
                    "captcha_input": "k7qmr"
                  }
                },
                "distribuidor": {
                  "summary": "Aplicación a distribuidores",
                  "value": {
                    "form_type": "distribuidor",
                    "ts": 1756800000000,
                    "nombre": "Luis Ortega",
                    "empresa": "Integra Soluciones",
                    "email": "luis@integra.mx",
                    "tipo_de_partner": "Integrador / casa de software",
                    "captcha_expected": "T4WNP",
                    "captcha_input": "T4WNP"
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/ContactRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Mensaje aceptado y entregado al equipo comercial.",
            "headers": {
              "Vary": {
                "schema": {
                  "type": "string"
                },
                "description": "Accept, Accept-Encoding, Origin"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                },
                "example": {
                  "ok": true,
                  "message": "¡Recibimos tu mensaje! Te contactamos pronto."
                }
              }
            }
          },
          "400": {
            "description": "Cuerpo inválido, captcha incorrecto, tipo de formulario desconocido o email mal formado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "bad_captcha": {
                    "value": {
                      "ok": false,
                      "error": {
                        "code": "bad_captcha",
                        "message": "El código de verificación no coincide.",
                        "status": 400,
                        "hint": "El captcha es solo para navegadores. Para integraciones automatizadas contacta a ventas@boolpay.mx y solicita acceso a la API."
                      },
                      "docs": "https://boolpay.mx/openapi.json"
                    }
                  },
                  "invalid_body": {
                    "value": {
                      "ok": false,
                      "error": {
                        "code": "invalid_body",
                        "message": "Solicitud inválida: el cuerpo está vacío o no es JSON válido.",
                        "status": 400,
                        "hint": "Envía un objeto JSON con al menos form_type, nombre y email. Ver https://boolpay.mx/openapi.json"
                      },
                      "docs": "https://boolpay.mx/openapi.json"
                    }
                  }
                }
              }
            }
          },
          "405": {
            "description": "Método no permitido: el endpoint solo acepta POST.",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                },
                "description": "POST, OPTIONS"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "method_not_allowed",
                    "message": "Método no permitido. Este endpoint solo acepta POST.",
                    "status": 405,
                    "hint": "Envía POST con Content-Type: application/json. El esquema del cuerpo está en https://boolpay.mx/openapi.json"
                  },
                  "docs": "https://boolpay.mx/openapi.json"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit por IP o ventana temporal fuera de rango.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Segundos a esperar antes de reintentar"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limit",
                    "message": "Límite de envíos alcanzado.",
                    "status": 429,
                    "hint": "Espera 30 segundos antes de enviar otro mensaje desde la misma IP."
                  },
                  "docs": "https://boolpay.mx/openapi.json"
                }
              }
            }
          },
          "500": {
            "description": "Fallo al entregar el correo o error interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "mail_delivery_failed",
                    "message": "No pudimos entregar tu mensaje al servidor de correo.",
                    "status": 500,
                    "hint": "Reintenta en unos minutos. Si persiste, escribe a ventas@boolpay.mx."
                  },
                  "docs": "https://boolpay.mx/openapi.json"
                }
              }
            }
          }
        }
      },
      "options": {
        "tags": [
          "contact"
        ],
        "operationId": "contactPreflight",
        "summary": "Preflight CORS",
        "responses": {
          "204": {
            "description": "Sin contenido."
          }
        }
      }
    },
    "/api": {
      "get": {
        "tags": [
          "discovery"
        ],
        "operationId": "getApiIndex",
        "summary": "Índice de la API",
        "description": "Documento de descubrimiento: versiones disponibles, política de versionado, rate limits y punteros a documentación. No requiere autenticación.",
        "responses": {
          "200": {
            "description": "Índice de la API.",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "Estado del límite, RFC 9331. Ej: \"default\";r=57;t=42"
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "Política declarada. Ej: \"default\";q=60;w=60"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones permitidas por ventana"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones restantes en la ventana"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Epoch en segundos en que se reinicia la ventana"
              },
              "API-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión de la API que atendió la petición"
              },
              "Vary": {
                "schema": {
                  "type": "string"
                },
                "description": "Accept, Accept-Encoding, Origin"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": {
                    "name": "BoolPay Public API",
                    "current_version": "v1",
                    "openapi": "https://boolpay.mx/openapi.json",
                    "rate_limit": {
                      "limit": 60,
                      "window_seconds": 60,
                      "scope": "ip"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit excedido.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "Estado del límite, RFC 9331. Ej: \"default\";r=57;t=42"
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "Política declarada. Ej: \"default\";q=60;w=60"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones permitidas por ventana"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones restantes en la ventana"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Epoch en segundos en que se reinicia la ventana"
              },
              "API-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión de la API que atendió la petición"
              },
              "Vary": {
                "schema": {
                  "type": "string"
                },
                "description": "Accept, Accept-Encoding, Origin"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/versioning": {
      "get": {
        "tags": [
          "discovery"
        ],
        "operationId": "getVersioningPolicy",
        "summary": "Política de versionado y deprecación",
        "description": "Estrategia de versionado, versiones soportadas y deprecadas, garantías de compatibilidad y cómo se señala la deprecación.",
        "responses": {
          "200": {
            "description": "Política de versionado.",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "Estado del límite, RFC 9331. Ej: \"default\";r=57;t=42"
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "Política declarada. Ej: \"default\";q=60;w=60"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones permitidas por ventana"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones restantes en la ventana"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Epoch en segundos en que se reinicia la ventana"
              },
              "API-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión de la API que atendió la petición"
              },
              "Vary": {
                "schema": {
                  "type": "string"
                },
                "description": "Accept, Accept-Encoding, Origin"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": {
                    "strategy": "url-path",
                    "current_version": "v1",
                    "supported": [
                      "v1"
                    ],
                    "deprecation_process": {
                      "notice_period_days": 180,
                      "after_sunset": "HTTP 410 Gone con error.code = \"api_version_sunset\"."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/health": {
      "get": {
        "tags": [
          "discovery"
        ],
        "operationId": "getHealth",
        "summary": "Estado de la plataforma",
        "responses": {
          "200": {
            "description": "Servicio operativo.",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "Estado del límite, RFC 9331. Ej: \"default\";r=57;t=42"
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "Política declarada. Ej: \"default\";q=60;w=60"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones permitidas por ventana"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones restantes en la ventana"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Epoch en segundos en que se reinicia la ventana"
              },
              "API-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión de la API que atendió la petición"
              },
              "Vary": {
                "schema": {
                  "type": "string"
                },
                "description": "Accept, Accept-Encoding, Origin"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "data": {
                    "status": "operational",
                    "version": "v1",
                    "timestamp": "2026-09-03T00:00:00+00:00",
                    "checks": {
                      "api": "ok",
                      "mail": "ok"
                    }
                  }
                }
              }
            }
          },
          "405": {
            "description": "Método no permitido.",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit excedido.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/contact": {
      "post": {
        "tags": [
          "contact"
        ],
        "operationId": "submitContactFormV1",
        "summary": "Enviar un formulario de contacto (v1)",
        "description": "Recibe una solicitud de demo, un mensaje a ventas, un onboarding de comercio o una aplicación al programa de distribuidores, y la entrega por correo al equipo comercial.\n\nProtección antispam activa: honeypot (`_bp_check`), ventana temporal (`ts`), captcha visual (`captcha_expected` / `captcha_input`) y rate limit de 30 s por IP. Las integraciones automatizadas no pueden resolver el captcha: solicita acceso a la API privada en ventas@boolpay.mx.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactRequest"
              },
              "examples": {
                "ventas": {
                  "summary": "Mensaje a ventas",
                  "value": {
                    "form_type": "ventas",
                    "ts": 1756800000000,
                    "nombre": "Ana Rivera",
                    "empresa": "Comercializadora Norte",
                    "email": "ana@empresa.mx",
                    "telefono": "+52 55 1234 5678",
                    "tema": "Propuesta comercial",
                    "mensaje": "Operamos 12 sucursales y buscamos terminales POS.",
                    "captcha_expected": "K7QMR",
                    "captcha_input": "k7qmr"
                  }
                },
                "distribuidor": {
                  "summary": "Aplicación a distribuidores",
                  "value": {
                    "form_type": "distribuidor",
                    "ts": 1756800000000,
                    "nombre": "Luis Ortega",
                    "empresa": "Integra Soluciones",
                    "email": "luis@integra.mx",
                    "tipo_de_partner": "Integrador / casa de software",
                    "captcha_expected": "T4WNP",
                    "captcha_input": "T4WNP"
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/ContactRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Mensaje aceptado y entregado al equipo comercial.",
            "headers": {
              "Vary": {
                "schema": {
                  "type": "string"
                },
                "description": "Accept, Accept-Encoding, Origin"
              },
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "Estado del límite, RFC 9331. Ej: \"default\";r=57;t=42"
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "Política declarada. Ej: \"default\";q=60;w=60"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones permitidas por ventana"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones restantes en la ventana"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Epoch en segundos en que se reinicia la ventana"
              },
              "API-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión de la API que atendió la petición"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                },
                "example": {
                  "ok": true,
                  "message": "¡Recibimos tu mensaje! Te contactamos pronto."
                }
              }
            }
          },
          "400": {
            "description": "Cuerpo inválido, captcha incorrecto, tipo de formulario desconocido o email mal formado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "bad_captcha": {
                    "value": {
                      "ok": false,
                      "error": {
                        "code": "bad_captcha",
                        "message": "El código de verificación no coincide.",
                        "status": 400,
                        "hint": "El captcha es solo para navegadores. Para integraciones automatizadas contacta a ventas@boolpay.mx y solicita acceso a la API."
                      },
                      "docs": "https://boolpay.mx/openapi.json"
                    }
                  },
                  "invalid_body": {
                    "value": {
                      "ok": false,
                      "error": {
                        "code": "invalid_body",
                        "message": "Solicitud inválida: el cuerpo está vacío o no es JSON válido.",
                        "status": 400,
                        "hint": "Envía un objeto JSON con al menos form_type, nombre y email. Ver https://boolpay.mx/openapi.json"
                      },
                      "docs": "https://boolpay.mx/openapi.json"
                    }
                  }
                }
              }
            },
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "Estado del límite, RFC 9331. Ej: \"default\";r=57;t=42"
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "Política declarada. Ej: \"default\";q=60;w=60"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones permitidas por ventana"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones restantes en la ventana"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Epoch en segundos en que se reinicia la ventana"
              },
              "API-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión de la API que atendió la petición"
              },
              "Vary": {
                "schema": {
                  "type": "string"
                },
                "description": "Accept, Accept-Encoding, Origin"
              }
            }
          },
          "405": {
            "description": "Método no permitido: el endpoint solo acepta POST.",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                },
                "description": "POST, OPTIONS"
              },
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "Estado del límite, RFC 9331. Ej: \"default\";r=57;t=42"
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "Política declarada. Ej: \"default\";q=60;w=60"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones permitidas por ventana"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones restantes en la ventana"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Epoch en segundos en que se reinicia la ventana"
              },
              "API-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión de la API que atendió la petición"
              },
              "Vary": {
                "schema": {
                  "type": "string"
                },
                "description": "Accept, Accept-Encoding, Origin"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "method_not_allowed",
                    "message": "Método no permitido. Este endpoint solo acepta POST.",
                    "status": 405,
                    "hint": "Envía POST con Content-Type: application/json. El esquema del cuerpo está en https://boolpay.mx/openapi.json"
                  },
                  "docs": "https://boolpay.mx/openapi.json"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit por IP o ventana temporal fuera de rango.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "Estado del límite, RFC 9331. Ej: \"default\";r=57;t=42"
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "Política declarada. Ej: \"default\";q=60;w=60"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones permitidas por ventana"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones restantes en la ventana"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Epoch en segundos en que se reinicia la ventana"
              },
              "API-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión de la API que atendió la petición"
              },
              "Vary": {
                "schema": {
                  "type": "string"
                },
                "description": "Accept, Accept-Encoding, Origin"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "rate_limit",
                    "message": "Límite de envíos alcanzado.",
                    "status": 429,
                    "hint": "Espera 30 segundos antes de enviar otro mensaje desde la misma IP."
                  },
                  "docs": "https://boolpay.mx/openapi.json"
                }
              }
            }
          },
          "500": {
            "description": "Fallo al entregar el correo o error interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "ok": false,
                  "error": {
                    "code": "mail_delivery_failed",
                    "message": "No pudimos entregar tu mensaje al servidor de correo.",
                    "status": 500,
                    "hint": "Reintenta en unos minutos. Si persiste, escribe a ventas@boolpay.mx."
                  },
                  "docs": "https://boolpay.mx/openapi.json"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "Estado del límite, RFC 9331. Ej: \"default\";r=57;t=42"
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "Política declarada. Ej: \"default\";q=60;w=60"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones permitidas por ventana"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Peticiones restantes en la ventana"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Epoch en segundos en que se reinicia la ventana"
              },
              "API-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión de la API que atendió la petición"
              },
              "Vary": {
                "schema": {
                  "type": "string"
                },
                "description": "Accept, Accept-Encoding, Origin"
              }
            }
          }
        }
      },
      "options": {
        "tags": [
          "contact"
        ],
        "operationId": "contactPreflight",
        "summary": "Preflight CORS",
        "responses": {
          "204": {
            "description": "Sin contenido."
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ContactRequest": {
        "type": "object",
        "required": [
          "form_type",
          "email"
        ],
        "additionalProperties": true,
        "properties": {
          "form_type": {
            "type": "string",
            "enum": [
              "demo",
              "ventas",
              "onboarding",
              "distribuidor"
            ],
            "description": "Formulario de origen."
          },
          "ts": {
            "type": "integer",
            "format": "int64",
            "description": "Timestamp en milisegundos de cuando se montó el formulario. Debe tener entre 2 s y 1 h de antigüedad."
          },
          "nombre": {
            "type": "string",
            "maxLength": 200
          },
          "empresa": {
            "type": "string",
            "maxLength": 200
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "telefono": {
            "type": "string",
            "maxLength": 40
          },
          "mensaje": {
            "type": "string",
            "maxLength": 5000
          },
          "captcha_expected": {
            "type": "string",
            "minLength": 5,
            "maxLength": 5,
            "description": "Código mostrado en el canvas del captcha."
          },
          "captcha_input": {
            "type": "string",
            "minLength": 5,
            "maxLength": 5,
            "description": "Código escrito por el usuario. Se compara sin distinguir mayúsculas."
          },
          "_bp_check": {
            "type": "string",
            "description": "Honeypot. Debe llegar vacío; si trae texto la petición se descarta silenciosamente."
          }
        }
      },
      "SuccessResponse": {
        "type": "object",
        "required": [
          "ok",
          "message"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "message": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "additionalProperties": true,
            "description": "Payload de la respuesta en los endpoints de /api/*."
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "ok",
          "error"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "status"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "method_not_allowed",
                  "invalid_body",
                  "bad_captcha",
                  "too_fast",
                  "rate_limit",
                  "invalid_form_type",
                  "invalid_email",
                  "mail_delivery_failed",
                  "internal_error",
                  "not_found",
                  "rate_limit_exceeded",
                  "endpoint_not_found",
                  "unsupported_api_version",
                  "api_version_sunset"
                ],
                "description": "Código de error estable, seguro para lógica automatizada."
              },
              "message": {
                "type": "string",
                "description": "Mensaje legible por humanos, en español."
              },
              "status": {
                "type": "integer",
                "description": "Código de estado HTTP repetido en el cuerpo."
              },
              "hint": {
                "type": "string",
                "description": "Cómo resolver el error."
              },
              "details": {
                "type": "object",
                "additionalProperties": true
              },
              "retry_after_seconds": {
                "type": "integer",
                "description": "Segundos a esperar antes de reintentar. Presente en errores 429."
              }
            }
          },
          "docs": {
            "type": "string",
            "format": "uri"
          }
        }
      }
    },
    "headers": {
      "RateLimit": {
        "schema": {
          "type": "string"
        },
        "description": "Estado del límite, RFC 9331. Ej: \"default\";r=57;t=42"
      },
      "RateLimitPolicy": {
        "schema": {
          "type": "string"
        },
        "description": "Política declarada. Ej: \"default\";q=60;w=60"
      },
      "RetryAfter": {
        "schema": {
          "type": "integer"
        },
        "description": "Segundos a esperar antes de reintentar"
      },
      "Deprecation": {
        "schema": {
          "type": "boolean"
        },
        "description": "RFC 9745. \"true\" si la versión está deprecada."
      },
      "Sunset": {
        "schema": {
          "type": "string"
        },
        "description": "RFC 8594. Fecha HTTP a partir de la cual la versión deja de responder."
      }
    }
  },
  "externalDocs": {
    "description": "Guía para agentes de IA: cuándo usar BoolPay, manejo de errores y rate limits",
    "url": "https://boolpay.mx/agent-instructions.md"
  }
}
