{"openapi":"3.1.0","info":{"title":"ZEVRA API de Captación","version":"1.0.0","summary":"Crea leads en tu CRM de ZEVRA desde tu sitio, tu formulario o tu automatización.","description":"Una sola operación, `POST /v1/leads`, servida de servidor a servidor con una llave por origen.\n\nSi el teléfono ya existe en tu organización, la solicitud enriquece ese lead sin reasignarlo; si no, crea uno nuevo y el motor de captación lo asigna según tus reglas.\n\nTodas las respuestas llevan `X-Request-Id`; cítalo al pedir soporte. Los errores usan un sobre único con un código estable (ver `components.responses.Error`).\n\nLa API no emite cabeceras CORS: llámala desde tu backend, nunca desde el navegador.","contact":{"name":"ZEVRA","url":"https://docs.zevra.co/integraciones/api-captacion"},"x-build":"c32283f42acd60d5d85f58cdda3f917ee3d6afc8"},"externalDocs":{"description":"Documentación en español","url":"https://docs.zevra.co/integraciones/api-captacion"},"servers":[{"url":"https://api.zevra.co","description":"Producción"}],"security":[{"bearerKey":[]}],"tags":[{"name":"Leads","description":"Alta de leads en el CRM."},{"name":"Meta","description":"Índice y contrato de la API."}],"paths":{"/v1":{"get":{"tags":["Meta"],"operationId":"getIndex","summary":"Índice de la API","description":"Nombre, versión, URL del documento OpenAPI y operaciones disponibles. Público.","security":[],"responses":{"200":{"description":"Índice.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexResponse"}}}}}}},"/v1/openapi.json":{"get":{"tags":["Meta"],"operationId":"getOpenApi","summary":"Este documento","description":"El contrato OpenAPI 3.1 de la API. Público, cacheable 5 minutos.","security":[],"responses":{"200":{"description":"Documento OpenAPI 3.1.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/leads":{"post":{"tags":["Leads"],"operationId":"createLead","summary":"Crear o enriquecer un lead","description":"Crea un lead en tu organización (201) o, si ya existe uno con el mismo teléfono, lo enriquece con los datos que falten (200) sin cambiar su responsable.\n\nEnvía `Idempotency-Key` para poder reintentar sin duplicar: la misma llave con el mismo cuerpo repite la respuesta original; con un cuerpo distinto responde 409.\n\nEnvía `X-Zevra-Dry-Run: true` para validar y ver qué pasaría sin escribir nada.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/DryRun"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadRequest"}}}},"responses":{"200":{"description":"Lead existente enriquecido, o resultado de una simulación (X-Zevra-Dry-Run).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/LeadEnriched"},{"$ref":"#/components/schemas/DryRunResult"}]}}}},"201":{"description":"Lead creado.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadCreated"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"413":{"$ref":"#/components/responses/Error"},"415":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"}},"x-codeSamples":[{"lang":"curl","label":"curl","source":"curl -X POST https://api.zevra.co/v1/leads \\\n  -H \"Authorization: Bearer zvr_live_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: mi-formulario-00123\" \\\n  -d '{\"nombre\":\"Ana Ruiz\",\"telefono\":\"+52 55 0000 0001\",\"email\":\"ana@ejemplo.com\",\"mensaje\":\"Me interesa agendar una visita.\",\"propiedad\":{\"codigo\":\"ZVR-1031\"}}'"},{"lang":"javascript","label":"Node.js (fetch)","source":"const response = await fetch(\"https://api.zevra.co/v1/leads\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer zvr_live_...\",\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": \"mi-formulario-00123\",\n  },\n  body: JSON.stringify({\n    \"nombre\": \"Ana Ruiz\",\n    \"telefono\": \"+52 55 0000 0001\",\n    \"email\": \"ana@ejemplo.com\",\n    \"mensaje\": \"Me interesa agendar una visita.\",\n    \"propiedad\": {\n      \"codigo\": \"ZVR-1031\"\n    }\n  }),\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(result.error.code + \": \" + result.error.message);\nconsole.log(result.status, result.lead_id);"},{"lang":"php","label":"PHP (curl)","source":"<?php\n$payload = [\n  \"nombre\" => \"Ana Ruiz\",\n  \"telefono\" => \"+52 55 0000 0001\",\n  \"email\" => \"ana@ejemplo.com\",\n  \"mensaje\" => \"Me interesa agendar una visita.\",\n  \"propiedad\" => [\n    \"codigo\" => \"ZVR-1031\",\n  ],\n];\n\n$ch = curl_init(\"https://api.zevra.co/v1/leads\");\ncurl_setopt_array($ch, [\n  CURLOPT_POST => true,\n  CURLOPT_RETURNTRANSFER => true,\n  CURLOPT_HTTPHEADER => [\n    \"Authorization: Bearer zvr_live_...\",\n    \"Content-Type: application/json\",\n    \"Idempotency-Key: mi-formulario-00123\",\n  ],\n  CURLOPT_POSTFIELDS => json_encode($payload),\n]);\n$raw = curl_exec($ch);\n$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);\ncurl_close($ch);\n\n$result = json_decode($raw, true);\nif ($status >= 400) {\n  throw new RuntimeException($result[\"error\"][\"code\"] . \": \" . $result[\"error\"][\"message\"]);\n}\necho $result[\"status\"] . \" \" . $result[\"lead_id\"];"}]}}},"components":{"securitySchemes":{"bearerKey":{"type":"http","scheme":"bearer","bearerFormat":"zvr_live_...","description":"La llave del origen (canal API en Captación). Se muestra una sola vez al generarla. Nunca la pongas en JavaScript del navegador."}},"schemas":{"LeadRequest":{"type":"object","properties":{"nombre":{"type":"string","minLength":1,"maxLength":200,"description":"Nombre del lead. Obligatorio.","examples":["Ana Ruiz"]},"telefono":{"description":"Teléfono del lead en cualquier formato con al menos 10 dígitos. Si ya existe un lead con ese teléfono en tu CRM, la solicitud lo enriquece (200 enriched) en lugar de crear uno nuevo.","examples":["+52 55 0000 0001"],"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"description":"Correo del lead. Se guarda en minúsculas.","examples":["ana@ejemplo.com"],"anyOf":[{"type":"string"},{"type":"null"}]},"mensaje":{"description":"Mensaje o comentario del lead. Se recorta a 2000 caracteres.","examples":["Me interesa agendar una visita este fin de semana."],"anyOf":[{"type":"string"},{"type":"null"}]},"propiedad":{"description":"Propiedad de interés (opcional). Ver el objeto: se requiere exactamente una referencia.","examples":[{"codigo":"ZVR-1031"}],"anyOf":[{"type":"object","properties":{"codigo":{"description":"Código ZEVRA del registro (ZVR-####).","examples":["ZVR-1031"],"type":"string"},"external_code":{"description":"Código propio del registro, tal como aparece en el catálogo de tu organización.","examples":["DV721"],"type":"string"},"id":{"description":"UUID interno del registro.","examples":["5b0b6a2e-0000-4000-8000-000000000000"],"type":"string"}},"description":"Propiedad de interés. Se requiere exactamente una de las tres referencias (codigo, external_code o id); se resuelve contra el catálogo de tu organización y una referencia desconocida responde 422 property_not_found.","examples":[{"codigo":"ZVR-1031"}]},{"type":"null"}]},"extras":{"description":"Campos adicionales de tu formulario (hasta 20). Cada par se agrega a las notas del lead como \"clave: valor\"; las claves se recortan a 60 caracteres y los valores a 200. Los valores vacíos se ignoran.","examples":[{"sede":"Polanco","presupuesto":3500000}],"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"}]}},{"type":"null"}]}},"required":["nombre"],"description":"Cuerpo de POST /v1/leads."},"LeadCreated":{"type":"object","properties":{"lead_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"UUID del lead en ZEVRA.","examples":["315f3db3-7c1e-4d2a-9b8f-0a1b2c3d4e5f"]},"status":{"type":"string","const":"created","description":"Se creó un lead nuevo."},"lead_url":{"type":"string","format":"uri","description":"Enlace directo al lead en el dashboard.","examples":["https://dashboard.zevra.co/dashboard/leads?leadId=315f3db3-7c1e-4d2a-9b8f-0a1b2c3d4e5f"]},"request_id":{"type":"string","description":"Identificador de la solicitud (también en la cabecera X-Request-Id). Cítalo al pedir soporte.","examples":["req_8f3a2c1d9e4b7a6c5d2e1f0a"]},"replay":{"description":"Presente (true) cuando la respuesta es la repetición de una solicitud anterior con la misma Idempotency-Key.","type":"boolean","const":true}},"required":["lead_id","status","lead_url","request_id"],"description":"201: lead creado."},"LeadEnriched":{"type":"object","properties":{"lead_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"UUID del lead en ZEVRA.","examples":["315f3db3-7c1e-4d2a-9b8f-0a1b2c3d4e5f"]},"status":{"type":"string","const":"enriched","description":"Ya existía un lead con ese teléfono; se enriqueció sin reasignarlo."},"matched_on":{"type":"string","const":"telefono","description":"Criterio con el que se encontró el lead existente. Hoy solo el teléfono."},"lead_url":{"type":"string","format":"uri","description":"Enlace directo al lead en el dashboard.","examples":["https://dashboard.zevra.co/dashboard/leads?leadId=315f3db3-7c1e-4d2a-9b8f-0a1b2c3d4e5f"]},"request_id":{"type":"string","description":"Identificador de la solicitud (también en la cabecera X-Request-Id). Cítalo al pedir soporte.","examples":["req_8f3a2c1d9e4b7a6c5d2e1f0a"]},"replay":{"description":"Presente (true) cuando la respuesta es la repetición de una solicitud anterior con la misma Idempotency-Key.","type":"boolean","const":true}},"required":["lead_id","status","matched_on","lead_url","request_id"],"description":"200: lead existente enriquecido."},"DryRunResult":{"type":"object","properties":{"status":{"type":"string","const":"dry_run","description":"Nada se escribió."},"would":{"type":"string","enum":["create","enrich"],"description":"Lo que haría la misma solicitud sin X-Zevra-Dry-Run."},"would_match":{"anyOf":[{"type":"object","properties":{"lead_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"UUID del lead en ZEVRA.","examples":["315f3db3-7c1e-4d2a-9b8f-0a1b2c3d4e5f"]},"matched_on":{"type":"string","const":"telefono","description":"Criterio de coincidencia."}},"required":["lead_id","matched_on"]},{"type":"null"}],"description":"El lead existente que se enriquecería, o null si se crearía uno nuevo."},"resolved":{"type":"object","properties":{"organizacion":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"UUID de tu organización."},"origen":{"type":"string","description":"Etiqueta del origen (la llave) que autenticó la solicitud.","examples":["Sitio PHP"]},"development_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}],"description":"UUID del registro al que se resolvió `propiedad`, o null."},"nombre":{"type":"string","description":"Nombre normalizado."},"telefono":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Teléfono tal como se recibió, o null."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Correo normalizado, o null."},"extras":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Extras normalizados (clave: valor)."}},"required":["organizacion","origen","development_id","nombre","telefono","email","extras"],"description":"Cómo se interpretó la solicitud."},"request_id":{"type":"string","description":"Identificador de la solicitud (también en la cabecera X-Request-Id). Cítalo al pedir soporte.","examples":["req_8f3a2c1d9e4b7a6c5d2e1f0a"]}},"required":["status","would","would_match","resolved","request_id"],"description":"200: simulación (X-Zevra-Dry-Run: true)."},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["validation_error","invalid_key","not_found","method_not_allowed","idempotency_conflict","idempotency_in_progress","payload_too_large","unsupported_media_type","property_not_found","rate_limited","internal_error","temporarily_unavailable"],"description":"Código estable de error. Úsalo para ramificar; el mensaje puede cambiar."},"message":{"type":"string","description":"Explicación en inglés para quien programa la integración.","examples":["nombre is required and must be a non-empty string"]},"field":{"description":"Campo o cabecera que falló (ruta con puntos).","examples":["propiedad.codigo"],"type":"string"},"docs_url":{"type":"string","format":"uri","description":"Explicación en español del código en la documentación.","examples":["https://docs.zevra.co/integraciones/api-captacion/errores#validation_error"]}},"required":["code","message","docs_url"],"description":"El error."},"request_id":{"type":"string","description":"Identificador de la solicitud (también en la cabecera X-Request-Id). Cítalo al pedir soporte.","examples":["req_8f3a2c1d9e4b7a6c5d2e1f0a"]}},"required":["error","request_id"],"description":"Sobre de error. Todas las respuestas 4xx y 5xx tienen esta forma."},"IndexResponse":{"type":"object","properties":{"name":{"type":"string","description":"Nombre de la API."},"version":{"type":"string","const":"1","description":"Versión mayor de la API."},"openapi":{"type":"string","format":"uri","description":"URL del documento OpenAPI."},"docs":{"type":"string","format":"uri","description":"URL de la documentación."},"endpoints":{"type":"array","items":{"type":"string"},"description":"Operaciones disponibles.","examples":[["POST /v1/leads"]]},"request_id":{"type":"string","description":"Identificador de la solicitud (también en la cabecera X-Request-Id). Cítalo al pedir soporte.","examples":["req_8f3a2c1d9e4b7a6c5d2e1f0a"]}},"required":["name","version","openapi","docs","endpoints","request_id"],"description":"GET /v1: índice de la API."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Identificador único de esta solicitud (1 a 200 caracteres ASCII imprimibles), por ejemplo el id del envío en tu formulario. Un reintento con la misma llave y el mismo cuerpo devuelve la respuesta original con `replay: true` durante 24 horas. Una solicitud fallida libera la llave. Se ignora en una simulación.","schema":{"type":"string","minLength":1,"maxLength":200,"examples":["mi-formulario-00123"]}},"DryRun":{"name":"X-Zevra-Dry-Run","in":"header","required":false,"description":"Con el valor `true`, la API valida, resuelve la propiedad y busca coincidencias, pero no escribe nada. Responde 200 con `status: dry_run`.","schema":{"type":"string","enum":["true"]}}},"headers":{"X-Request-Id":{"description":"Identificador de la solicitud, generado por ZEVRA. Cítalo al pedir soporte.","schema":{"type":"string","examples":["req_8f3a2c1d9e4b7a6c5d2e1f0a"]}},"X-RateLimit-Limit":{"description":"Solicitudes permitidas por minuto para esta llave.","schema":{"type":"integer","examples":[60]}},"X-RateLimit-Remaining":{"description":"Solicitudes restantes en la ventana actual.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Momento (Unix, segundos) en que se reinicia la ventana.","schema":{"type":"integer"}},"Retry-After":{"description":"Segundos que conviene esperar antes de reintentar.","schema":{"type":"integer"}}},"responses":{"Error":{"description":"Error. El cuerpo siempre es el sobre `Error`; `error.code` es estable y `error.docs_url` apunta a su explicación en https://docs.zevra.co/integraciones/api-captacion/errores.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":{"validation_error":{"status":400,"description":"El cuerpo o una cabecera no pasó la validación. El campo `field` indica cuál (ruta con puntos, por ejemplo `propiedad.codigo`)."},"invalid_key":{"status":401,"description":"Falta la llave, está mal formada o no corresponde a ningún origen activo."},"not_found":{"status":404,"description":"La ruta no existe en esta API. Solo se sirven rutas bajo `/v1`."},"method_not_allowed":{"status":405,"description":"La ruta existe pero no acepta ese método. La cabecera `Allow` lista los permitidos."},"idempotency_conflict":{"status":409,"description":"Ya se usó esa `Idempotency-Key` con un cuerpo distinto. Usa una llave nueva para una solicitud nueva."},"idempotency_in_progress":{"status":409,"description":"La solicitud original con esa `Idempotency-Key` sigue en curso. Reintenta en un segundo."},"payload_too_large":{"status":413,"description":"El cuerpo supera los 64 000 bytes."},"unsupported_media_type":{"status":415,"description":"El `Content-Type` debe ser `application/json`."},"property_not_found":{"status":422,"description":"La propiedad indicada no existe en el catálogo de tu organización."},"rate_limited":{"status":429,"description":"Superaste las 60 solicitudes por minuto de esta llave. La cabecera `Retry-After` indica cuándo reintentar."},"internal_error":{"status":500,"description":"ZEVRA falló al procesar la solicitud. Reintenta con la misma `Idempotency-Key`; la falla no dejó lead."},"temporarily_unavailable":{"status":503,"description":"ZEVRA no pudo consultar su base de datos. Reintenta después de los segundos que indica `Retry-After`."}}}}}}