{"openapi":"3.0.0","paths":{"/api/v1/licitaciones":{"get":{"description":"Devuelve una lista paginada de licitaciones filtradas por los parámetros indicados.","operationId":"V1LicitacionesController_search","parameters":[{"name":"limit","required":false,"in":"query","description":"Resultados por página (20 por defecto, máx 100)","schema":{"type":"number"}},{"name":"page","required":false,"in":"query","description":"Página (1-based)","schema":{"type":"number"}},{"name":"conPliego","required":false,"in":"query","description":"true = solo licitaciones con pliego descargable (las únicas analizables con POST /v1/analisis)","schema":{"type":"boolean"}},{"name":"organismo","required":false,"in":"query","description":"Órgano o entidad contratante (texto)","schema":{}},{"name":"provincia","required":false,"in":"query","description":"Nombre de la provincia","schema":{}},{"name":"presupuestoMax","required":false,"in":"query","description":"Euros sin IVA, sobre presupuestoBase","schema":{"type":"number"}},{"name":"presupuestoMin","required":false,"in":"query","description":"Euros sin IVA, sobre presupuestoBase","schema":{"type":"number"}},{"name":"estado","required":false,"in":"query","description":"Estado; para «abiertas» usa en_plazo","schema":{"enum":["publicada","en_plazo","evaluacion","adjudicada","desierta","anulada"],"type":"string"}},{"name":"tipoContrato","required":false,"in":"query","description":"Tipo de contrato","schema":{"enum":["servicios","suministros","obras","concesion","mixto"],"type":"string"}},{"name":"cpv","required":false,"in":"query","description":"Código CPV (8 dígitos)","schema":{}},{"name":"query","required":false,"in":"query","description":"Texto libre","schema":{}}],"responses":{"200":{"description":"Lista de licitaciones","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LicitacionesPaginaRespuestaDto"},"example":{"data":[{"id":"00000000-0000-4000-8000-000000000001","title":"Servicio de mantenimiento de zonas verdes municipales","organismo":"Ayuntamiento de Ejemplo","organoContratacion":"Junta de Gobierno Local","presupuestoBase":245000,"valorEstimado":490000,"tipoContrato":"servicios","procedimiento":"abierto","estado":"en_plazo","cpvCode":"77310000","fechaPublicacion":"2026-09-20T00:00:00.000Z","fechaLimite":"2026-10-15T00:00:00.000Z","urlAnuncio":"https://contrataciondelestado.es/…","plataforma":"PLACE"}],"total":1,"page":1,"limit":20,"totalPages":1}}}},"400":{"description":"Parámetros de búsqueda inválidos"},"401":{"description":"API key inválida o ausente"},"403":{"description":"SCOPE_REQUIRED (sin licitaciones:read) o PLAN_REQUIRED"},"429":{"description":"RATE_LIMIT o QUOTA_EXCEEDED (ver Retry-After)"}},"security":[{"x-api-key":[]}],"summary":"Buscar licitaciones","tags":["v1 / Licitaciones"],"x-licitaia":{"scope":"licitaciones:read","cobra":false,"soloLectura":true}}},"/api/v1/licitaciones/{id}":{"get":{"description":"Devuelve el detalle completo de una licitación, incluyendo la clasificación de umbral LCSP.","operationId":"V1LicitacionesController_findOne","parameters":[{"name":"id","required":true,"in":"path","description":"UUID de la licitación","schema":{"type":"string"}}],"responses":{"200":{"description":"Detalle de la licitación con clasificación de umbral","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LicitacionDetalleRespuestaDto"},"example":{"data":{"id":"00000000-0000-4000-8000-000000000001","title":"Servicio de mantenimiento de zonas verdes municipales","organismo":"Ayuntamiento de Ejemplo","organoContratacion":"Junta de Gobierno Local","presupuestoBase":245000,"valorEstimado":490000,"tipoContrato":"servicios","procedimiento":"abierto","estado":"en_plazo","cpvCode":"77310000","fechaPublicacion":"2026-09-20T00:00:00.000Z","fechaLimite":"2026-10-15T00:00:00.000Z","urlAnuncio":"https://contrataciondelestado.es/…","plataforma":"PLACE","description":"Mantenimiento integral de parques y jardines municipales.","urlPliego":"https://contrataciondelestado.es/…/PCAP.pdf","documentos":[{"tipo":"PCAP","url":"https://contrataciondelestado.es/…/PCAP.pdf"},{"tipo":"PPT","url":"https://contrataciondelestado.es/…/PPT.pdf"}],"clasificacionUmbral":{"procedimiento":"abierto","sara":false,"umbralSara":216000}}}}}},"401":{"description":"API key inválida o ausente"},"403":{"description":"SCOPE_REQUIRED (sin licitaciones:read) o PLAN_REQUIRED"},"404":{"description":"Licitación no encontrada"},"429":{"description":"RATE_LIMIT o QUOTA_EXCEEDED (ver Retry-After)"}},"security":[{"x-api-key":[]}],"summary":"Obtener licitación por ID","tags":["v1 / Licitaciones"],"x-licitaia":{"scope":"licitaciones:read","cobra":false,"soloLectura":true}}},"/api/v1/recomendaciones":{"get":{"description":"Las mismas recomendadas que el panel y el copilot (hasta 50, ordenadas por afinidad, con sus motivos). La empresa es la activa del dueño de la clave (o la por defecto de la organización), no la asociada a la clave: meta.companyId dice cuál se usó y meta.companyIdClave aparece si la clave tiene otra. El digest por correo usa reglas propias (preferencias de alerta, ventana, máximo 25), así que no tiene por qué coincidir. La lista no trae documentos ni requisitos: la ficha completa está en /v1/licitaciones/{id}.","operationId":"V1RecomendacionesController_listar","parameters":[{"name":"limit","required":false,"in":"query","description":"Máximo de recomendaciones a devolver (1-50). Sin él, todas las que da el panel (hasta 50).","schema":{"minimum":1,"maximum":50,"type":"number"}}],"responses":{"200":{"description":"Recomendadas con score y motivos","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecomendacionesRespuestaDto"},"example":{"data":[{"licitacion":{"id":"00000000-0000-4000-8000-000000000001","title":"Servicio de mantenimiento de zonas verdes municipales","organismo":"Ayuntamiento de Ejemplo","presupuestoBase":245000,"fechaLimite":"2026-10-15T00:00:00.000Z","cpvCode":"77310000","urlAnuncio":"https://contrataciondelestado.es/…","url":"https://licitaia.org/licitaciones/00000000-0000-4000-8000-000000000001/"},"score":86,"reasons":["CPV 77310000 coincide con tu perfil","Presupuesto dentro de tu rango"]}],"meta":{"companyId":"00000000-0000-4000-8000-000000000003","generatedAt":"2026-09-29T09:00:00.000Z","total":1}}}}},"400":{"description":"limit fuera de 1-50"},"401":{"description":"API key inválida, revocada o ausente"},"403":{"description":"SCOPE_REQUIRED (sin recomendaciones:read) o PLAN_REQUIRED"},"404":{"description":"La organización no tiene perfil de empresa"},"429":{"description":"RATE_LIMIT o QUOTA_EXCEEDED (ver Retry-After)"}},"security":[{"x-api-key":[]}],"summary":"Licitaciones recomendadas para tu empresa","tags":["v1 / Recomendaciones"],"x-licitaia":{"scope":"recomendaciones:read","cobra":false,"soloLectura":true}}},"/api/v1/analisis":{"post":{"description":"Lanza (o recupera) el análisis de los pliegos oficiales de la licitación, con el mismo cobro que la ficha (2 créditos; la caché compartida y los análisis que tu organización ya tiene no se vuelven a cobrar). Sin Idempotency-Key, pedir otra vez la misma licitación devuelve el análisis existente. Con Idempotency-Key, un reintento en 24 h devuelve el mismo análisis con idempotente: true.","operationId":"V1AnalisisController_crear","parameters":[{"name":"Idempotency-Key","in":"header","description":"Clave del cliente para reintentos seguros (máx. 128 caracteres, 24 h).","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrearAnalisisApiDto"}}}},"responses":{"202":{"description":"Análisis en cola o ya disponible","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrearAnalisisRespuestaDto"},"example":{"data":{"id":"00000000-0000-4000-8000-000000000002","status":"pending","licitacionId":"00000000-0000-4000-8000-000000000001","cached":false},"idempotente":false}}}},"400":{"description":"Cuerpo inválido o Idempotency-Key de más de 128 caracteres"},"401":{"description":"API key inválida, revocada o ausente"},"403":{"description":"SCOPE_REQUIRED (sin analisis:write), PLAN_REQUIRED o créditos insuficientes"},"404":{"description":"Licitación no encontrada"},"422":{"description":"La licitación no publica pliegos descargables, o IDEMPOTENCY_KEY_REUSED (la clave ya se usó con otra licitación u otro modo)"},"429":{"description":"RATE_LIMIT o QUOTA_EXCEEDED (ver Retry-After)"}},"security":[{"x-api-key":[]}],"summary":"Analizar los pliegos de una licitación","tags":["v1 / Analisis"],"x-licitaia":{"scope":"analisis:write","cobra":true,"creditos":2,"soloLectura":false}}},"/api/v1/analisis/{id}":{"get":{"description":"El análisis de tu organización, con la misma restricción por plan o derecho de evaluación que la web. No cobra.","operationId":"V1AnalisisController_leer","parameters":[{"name":"id","required":true,"in":"path","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"Análisis","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalisisRespuestaDto"},"example":{"data":{"id":"00000000-0000-4000-8000-000000000002","status":"completed","licitacionId":"00000000-0000-4000-8000-000000000001","fileName":"PLACE-EXP-2026-0042 PCAP+PPT","resumenEjecutivo":"Contrato de servicios de mantenimiento de zonas verdes por dos años prorrogables…","objetoContrato":"Mantenimiento integral de parques y jardines municipales.","requisitosSolvencia":{"economica":{"facturacionMinima":"367.500 €","seguroRC":"245.000 €"},"tecnica":{"experiencia":"Tres trabajos similares en los últimos tres años","certificaciones":["ISO 9001"],"certificacionesProducto":[],"otrosRequisitos":[]}},"criteriosAdjudicacion":[{"nombre":"Oferta económica","puntos":60,"tipo":"formula"},{"nombre":"Memoria técnica","puntos":40,"tipo":"juicio_valor"}],"plazosClave":[{"nombre":"Fin de presentación de ofertas","fecha":"2026-10-15","descripcion":"Hasta las 14:00"}],"penalizaciones":[],"clausulasRiesgo":[],"elegibilidad":{"cumple":null,"requisitos":[]},"recomendaciones":["Revisa el seguro de RC antes de presentar."],"documentosRequeridos":[],"entregables":[],"requisitosMinimos":[],"garantias":{"provisional":{"exigida":false,"importe":null,"descripcion":null},"definitiva":{"exigida":true,"importe":"5 % del precio de adjudicación","descripcion":null},"complementaria":{"exigida":false,"importe":null,"descripcion":null}},"cpvCodes":["77310000"],"cobertura":{"recortado":false,"documentos":[{"tipo":"PCAP","nombre":"PCAP.pdf","paginasTotales":48,"paginasEnviadas":48},{"tipo":"PPT","nombre":"PPT.pdf","paginasTotales":20,"paginasEnviadas":20}]},"lotes":null,"creditsUsed":2,"modelVersion":"claude-sonnet-5/2026-09-i","createdAt":"2026-09-29T08:24:10.000Z","updatedAt":"2026-09-29T08:25:02.000Z"}}}}},"400":{"description":"id no es un UUID"},"401":{"description":"API key inválida, revocada o ausente"},"403":{"description":"SCOPE_REQUIRED o PLAN_REQUIRED"},"404":{"description":"No existe o es de otra organización"},"429":{"description":"RATE_LIMIT o QUOTA_EXCEEDED (ver Retry-After)"}},"security":[{"x-api-key":[]}],"summary":"Leer un análisis","tags":["v1 / Analisis"],"x-licitaia":{"scope":"analisis:read","cobra":false,"soloLectura":true}}},"/api/v1/analisis/{id}/status":{"get":{"description":"Estado y etapa real del worker (descargando, leyendo, analizando) mientras corre. Para sondear. No cobra.","operationId":"V1AnalisisController_estado","parameters":[{"name":"id","required":true,"in":"path","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"{ id, status, etapa, iniciadoEn, errorMessage? }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EstadoAnalisisRespuestaDto"},"example":{"id":"00000000-0000-4000-8000-000000000002","status":"processing","etapa":"analizando","iniciadoEn":"2026-09-29T08:24:10.000Z"}}}},"400":{"description":"id no es un UUID"},"401":{"description":"API key inválida, revocada o ausente"},"403":{"description":"SCOPE_REQUIRED o PLAN_REQUIRED"},"404":{"description":"No existe o es de otra organización"},"429":{"description":"RATE_LIMIT o QUOTA_EXCEEDED (ver Retry-After)"}},"security":[{"x-api-key":[]}],"summary":"Estado de un análisis","tags":["v1 / Analisis"],"x-licitaia":{"scope":"analisis:read","cobra":false,"soloLectura":true}}},"/api/v1/licitaciones/{id}/veredicto":{"get":{"description":"El veredicto más reciente de la empresa del dueño de la clave para esta licitación, tal y como lo ve la ficha web. meta.stale = true significa que el perfil de empresa cambió desde entonces en algo que el motor consulta: recalcula con POST. No cobra ni llama al modelo. Si nunca se calculó, 404 SIN_VEREDICTO con el siguiente paso en el cuerpo.","operationId":"V1VeredictosController_leer","parameters":[{"name":"id","required":true,"in":"path","description":"Id de la licitación","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"Veredicto guardado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VeredictoRespuestaDto"},"example":{"data":{"decision":"REVIEW","score":23,"checks":[{"categoria":"solvencia_economica","requisito":"Seguro de responsabilidad civil por importe igual o superior al valor estimado","cumple":null,"razonNull":"perfil_incompleto","detalle":"No se ha registrado importe de seguro RC en el perfil de empresa. Completa el campo \"seguroRcImporte\".","importancia":"critica","naturaleza":"solvencia"},{"categoria":"certificaciones","requisito":"Certificación ISO 9001 en vigor","cumple":false,"detalle":"La empresa no tiene registrada la certificación ISO 9001.","importancia":"importante","naturaleza":"solvencia"}],"summary":"REVISION NECESARIA. 1 requisito(s) critico(s) no verificados por perfil incompleto; 1 requisito(s) importante(s) no cumplidos.","missingProfileFields":["seguro de responsabilidad civil (importe)"],"missingProfileKeys":["seguroRcImporte"],"ambiguousRequirements":[]},"meta":{"licitacionId":"00000000-0000-4000-8000-000000000001","companyId":"00000000-0000-4000-8000-000000000003","calculatedAt":"2026-09-29T08:26:29.218Z","stale":false}}}}},"400":{"description":"id no es un UUID"},"401":{"description":"API key inválida, revocada o ausente"},"403":{"description":"SCOPE_REQUIRED (sin veredictos:read) o PLAN_REQUIRED"},"404":{"description":"SIN_VEREDICTO (nunca calculado para esta empresa; el cuerpo trae siguientePaso) o EMPRESA_REQUERIDA (la organización no tiene perfil de empresa)"},"429":{"description":"RATE_LIMIT o QUOTA_EXCEEDED (ver Retry-After)"}},"security":[{"x-api-key":[]}],"summary":"Último veredicto GO/NO_GO guardado","tags":["v1 / Veredicto"],"x-licitaia":{"scope":"veredictos:read","cobra":false,"soloLectura":true}},"post":{"description":"Compara los requisitos de solvencia del pliego con el perfil de la empresa del dueño de la clave y guarda el resultado. Determinista: no llama al modelo y no cobra créditos. A diferencia de la web, NO lanza la lectura del pliego oficial cuando la licitación aún no tiene requisitos estructurados: en ese caso el veredicto llega con 0 checks y siguientePaso indica que hay que lanzar POST /v1/analisis (scope analisis:write, 2 créditos) y volver a pedir el veredicto cuando termine. Respuesta 200 aunque recalcule: el recurso es el veredicto, no un job.","operationId":"V1VeredictosController_calcular","parameters":[{"name":"id","required":true,"in":"path","description":"Id de la licitación","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"Veredicto recién calculado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VeredictoRespuestaDto"},"example":{"data":{"decision":"REVIEW","score":50,"checks":[],"summary":"Sin requisitos estructurados.","missingProfileFields":["seguro de responsabilidad civil (importe)"],"missingProfileKeys":["seguroRcImporte"],"ambiguousRequirements":[]},"meta":{"licitacionId":"00000000-0000-4000-8000-000000000001","companyId":"00000000-0000-4000-8000-000000000003","calculatedAt":"2026-09-29T08:26:29.218Z","stale":false},"siguientePaso":{"accion":"analizar_pliegos","metodo":"POST","ruta":"/api/v1/analisis","body":{"licitacionId":"00000000-0000-4000-8000-000000000001"},"scope":"analisis:write","motivo":"No hay requisitos de solvencia estructurados para esta licitación. Analizar sus pliegos oficiales (2 créditos) los extrae; después vuelve a pedir el veredicto."}}}}},"400":{"description":"id no es un UUID"},"401":{"description":"API key inválida, revocada o ausente"},"403":{"description":"SCOPE_REQUIRED (sin veredictos:read) o PLAN_REQUIRED"},"404":{"description":"Licitación no encontrada o EMPRESA_REQUERIDA (la organización no tiene perfil de empresa)"},"429":{"description":"RATE_LIMIT o QUOTA_EXCEEDED (ver Retry-After)"}},"security":[{"x-api-key":[]}],"summary":"Calcular (o recalcular) el veredicto GO/NO_GO","tags":["v1 / Veredicto"],"x-licitaia":{"scope":"veredictos:read","cobra":false,"soloLectura":false}}}},"info":{"title":"LicitaIA API v1","description":"API pública de LicitaIA (piloto). Licitaciones españolas y europeas,\nrecomendaciones por empresa y análisis de pliegos con IA, integrables en tus\npropias aplicaciones.\n\n## Autenticación\n\nCabecera `x-api-key` con una clave con prefijo `lic_live_`. Sin ella, o con\nuna clave inválida: 401 sin `code`; revocada: 401 `API_KEY_REVOCADA`;\ncaducada: 401 `API_KEY_CADUCADA` (rótala o crea otra). La clave se crea desde la app\n(`POST /api-keys`, solo owner/admin de la organización) y se consulta el\nconsumo del mes con `GET /api-keys/uso`.\n\n## Quién tiene API\n\nPlanes **Pro**, **Team** y **AAPP** (plan efectivo). La prueba de Pro de 14\ndías al alta cuenta como Pro mientras dura. Al terminar la prueba o el plan\nsin renovar: 403 `PLAN_REQUIRED`. El plan **Free no tiene acceso a la API**.\n\n## Permisos (scopes)\n\nCada clave lleva uno o varios: `licitaciones:read`, `recomendaciones:read`,\n`analisis:read`, `analisis:write`, `veredictos:read`. Una llamada a una\nruta sin el scope necesario responde 403 `SCOPE_REQUIRED`.\n\n## Veredicto GO/NO_GO\n\n`GET /v1/licitaciones/{id}/veredicto` devuelve el último veredicto guardado\nde la empresa del dueño de la clave (`meta.stale` avisa si el perfil cambió\ndesde entonces); `POST` al mismo recurso lo (re)calcula. Es el mismo motor\ndeterminista que la ficha web: compara los requisitos de solvencia del pliego\ncon el perfil de empresa, **no llama al modelo y no cobra créditos**. Si la\nlicitación no tiene requisitos estructurados todavía, el veredicto llega con\n0 checks y un `siguientePaso` que dice cómo desbloquearlo (lanzar\n`POST /v1/analisis`, que sí cobra, y volver a pedir el veredicto).\nErrores propios: 404 `SIN_VEREDICTO` (nunca calculado; el cuerpo trae el\n`siguientePaso`) y 404 `EMPRESA_REQUERIDA` (la organización no tiene\nperfil de empresa: sin perfil no hay nada que comparar).\n\n## Cuotas\n\n| Plan | Peticiones/mes | Peticiones/minuto | Claves activas |\n|---|---|---|---|\n| Pro | 5.000 | 60 | 2 |\n| Team / AAPP | 50.000 | 300 | 10 |\n\nLa cuota **mensual es por ORGANIZACIÓN**: rotar o añadir claves no la\nmultiplica, todas las claves de la organización comparten el mismo contador.\nEl límite **por minuto es por CLAVE**.\n\nToda respuesta autenticada lleva `X-RateLimit-Limit-Month`,\n`X-RateLimit-Remaining-Month` y `X-RateLimit-Reset-Month` (epoch en\nsegundos). Un rechazo por 429 lleva además `Retry-After` y el cuerpo\n`{ code: 'RATE_LIMIT' | 'QUOTA_EXCEEDED', message, retryAfter, docs_url }`.\nEn un 429 `RATE_LIMIT` (ráfaga por minuto) el contador mensual no se ha\nleído, así que **`X-RateLimit-Remaining-Month` se omite a propósito** en esa\nrespuesta concreta. Una petición rechazada con 400 por validación **sí\nconsume cuota mensual**: los guards de clave/permiso/cuota corren antes que\nlos pipes de validación del cuerpo.\n\nLas rutas v1 están fuera del limitador global por IP de la plataforma, así\nque no llevan las cabeceras `X-RateLimit-*` genéricas: los únicos límites son\nla cuota mensual (cabeceras `-Month`) y la ráfaga por minuto. Un\n`x-api-key` sin la forma de una clave (`lic_live_` + 32 hex) da 401 sin\ntocar la BD. Si el dueño de la clave deja de ser miembro activo de la\norganización, 401 `CLAVE_SIN_DUENO`: rótala.\n\n## Idempotencia\n\n`POST /v1/analisis` acepta la cabecera `Idempotency-Key` (máx. 128\ncaracteres), válida 24 h. Un reintento con la misma clave debe llevar el\nmismo `licitacionId` y el mismo `modo`; si no, 422\n`IDEMPOTENCY_KEY_REUSED`. Sin Redis disponible la idempotencia por cabecera\nno funciona, pero el servicio sigue devolviendo el análisis existente de tu\norganización para esa licitación (mismo resultado práctico, sin la garantía\nde la cabecera).\n\n## Recomendaciones\n\n`GET /v1/recomendaciones` devuelve lo mismo que ve el panel web\n(`matchForCompany`) para la empresa ACTIVA del dueño de la clave (o la por\ndefecto de su organización) — no la empresa asociada a la clave.\n`meta.companyId` dice qué empresa se usó; `meta.companyIdClave` aparece\nsolo si la clave tiene otra empresa asociada. `score` es la afinidad 0-100\nnormalizada sobre los criterios efectivos del perfil (CPV, provincia,\npresupuesto, tipo) y las preferencias de alerta del dueño de la clave — el\nmismo cálculo que usa el digest de alertas por correo; `reasons` son sus\nmotivos. El digest de alertas por correo aplica su propia ventana de tiempo,\nsu propia selección de candidatas y un máximo de 25 resultados, así que la\nLISTA puede diferir de esta respuesta aunque el cálculo del `score` por\nlicitación sea idéntico.\n\n## Webhook `alert.digest`\n\nTeam y AAPP con una integración activa reciben el evento `alert.digest` tras\ncada digest, con identificadores únicamente (`companyId`,\n`licitacionIds`, ventana, frecuencia) — el contenido se resuelve llamando a\n`GET /v1/licitaciones/{id}`. Llega un evento por digest de cada miembro con\nalertas por correo; `destinatario` (hash opaco, sin datos personales) los\ndistingue.\n\n## Para agentes (capa 0, 2026-09-29)\n\nCada operación lleva la extensión `x-licitaia` con `scope`, `cobra`\n(true solo en `POST /v1/analisis`, con `creditos`) y `soloLectura`, y un\n`example` de respuesta con ids sintéticos. Esta misma referencia existe en\nMarkdown en `/api/v1/docs.md` (o en `/api/v1/docs` con\n`Accept: text/markdown`), el índice para agentes en `/llms.txt` y el\ncatálogo RFC 9727 en `/.well-known/api-catalog`. Las fichas públicas del\nsitio también se sirven en Markdown: `https://licitaia.org/licitaciones/{id}.md`.\nPara agentes de programación (Claude Code, Codex, Copilot, Cursor…) hay una\n**skill oficial** en el estándar Agent Skills:\n`https://licitaia.org/skills/licitaia-api/SKILL.md` (carpeta\n`frontend/public/skills/licitaia-api/` del repositorio), con los flujos en curl, los errores y las reglas de coste.\n\n## Errores\n\n400 (`IDEMPOTENCY_KEY_INVALID`), 401 (sin clave / clave inválida /\n`API_KEY_REVOCADA` / `API_KEY_CADUCADA` / `CLAVE_SIN_DUENO`), 403 (`PLAN_REQUIRED` / `SCOPE_REQUIRED` / créditos\ninsuficientes en `POST /v1/analisis`),\n404 (recurso no existe o es de otra organización), 409 (conflicto de\nestado), 422 (petición semánticamente inválida, p. ej.\n`IDEMPOTENCY_KEY_REUSED` o licitación sin pliegos), 429 (`RATE_LIMIT` /\n`QUOTA_EXCEEDED`).\n\n## Versionado\n\nv1 es estable. Los cambios incompatibles llegarán en `/v2`; toda\ndeprecación de v1 se anuncia en esta misma documentación con **al menos 90\ndías** de antelación.\n\n## Límites del piloto\n\nSin subida de ficheros por API (el análisis parte siempre de una licitación\nya ingerida). Hay **servidor MCP** (Streamable HTTP sin estado) en\n`POST https://api.licitaia.org/api/mcp` con la misma clave\n(`x-api-key` o `Authorization: Bearer lic_live_…`), mismos scopes y misma\ncuota; solo con clave, sin OAuth (no listado en directorios).\n\n## Ejemplos\n\nTodos los ejemplos de esta documentación usan identificadores sintéticos\n(`00000000-0000-4000-8000-000000000001` y variantes) y ninguna clave real.\n\n```bash\ncurl https://api.licitaia.org/api/v1/licitaciones \\\n  -H \"x-api-key: lic_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\n```","version":"1.0","contact":{"name":"LicitaIA","url":"https://licitaia.org","email":"api@licitaia.org"}},"tags":[],"servers":[{"url":"https://api.licitaia.org","description":"Producción"}],"components":{"securitySchemes":{"x-api-key":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"LicitacionesPaginaRespuestaDto":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Licitaciones de la página"},"total":{"type":"number","description":"Total de resultados"},"page":{"type":"number"},"limit":{"type":"number"},"totalPages":{"type":"number"}},"required":["data","total","page","limit","totalPages"]},"LicitacionDetalleRespuestaDto":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true,"description":"Ficha completa de la licitación, con `clasificacionUmbral` (LCSP) si hay tipo y presupuesto"}},"required":["data"]},"LicitacionRecomendadaDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"organismo":{"type":"string","nullable":true},"presupuestoBase":{"type":"number","nullable":true,"description":"Presupuesto base de licitación, sin IVA (EUR)"},"fechaLimite":{"type":"string","format":"date-time","nullable":true},"cpvCode":{"type":"string","nullable":true,"description":"CPV (8 dígitos)"},"urlAnuncio":{"type":"string","nullable":true,"description":"Anuncio en la plataforma oficial"},"url":{"type":"string","description":"Ficha en licitaia.org"}},"required":["id","title","organismo","presupuestoBase","fechaLimite","cpvCode","urlAnuncio","url"]},"RecomendacionDto":{"type":"object","properties":{"licitacion":{"$ref":"#/components/schemas/LicitacionRecomendadaDto"},"score":{"type":"number","description":"Afinidad 0-100 sobre el perfil y las preferencias de alerta del dueño de la clave (mismo cálculo que el digest de alertas)"},"reasons":{"description":"Motivos, en el orden en que suman a la afinidad","type":"array","items":{"type":"string"}}},"required":["licitacion","score","reasons"]},"RecomendacionesMetaDto":{"type":"object","properties":{"companyId":{"type":"string","format":"uuid","nullable":true,"description":"Empresa con la que se calcularon: la activa del dueño de la clave (o la por defecto de la organización)"},"companyIdClave":{"type":"string","format":"uuid","description":"Solo si la clave tiene una empresa asociada distinta de la usada: esa empresa"},"generatedAt":{"type":"string","format":"date-time"},"total":{"type":"number","description":"Recomendadas disponibles antes de aplicar limit"}},"required":["companyId","generatedAt","total"]},"RecomendacionesRespuestaDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RecomendacionDto"}},"meta":{"$ref":"#/components/schemas/RecomendacionesMetaDto"}},"required":["data","meta"]},"CrearAnalisisApiDto":{"type":"object","properties":{"licitacionId":{"type":"string","format":"uuid","description":"Id de la licitación (el de /v1/licitaciones)."},"modo":{"type":"string","enum":["evaluar","extraer"],"default":"evaluar","description":"'evaluar' (extracción y evaluación de encaje, por defecto) o 'extraer' (solo extracción). Mismo cobro que la web."}},"required":["licitacionId"]},"AnalisisCreadoDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Id del análisis"},"status":{"type":"string","enum":["pending","processing","completed","failed"]},"licitacionId":{"type":"string","format":"uuid"},"cached":{"type":"boolean","description":"true si el resultado ya estaba disponible (caché de la plataforma o análisis previo de tu organización)"}},"required":["id","status","licitacionId","cached"]},"CrearAnalisisRespuestaDto":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AnalisisCreadoDto"},"idempotente":{"type":"boolean","description":"true si es la respuesta a un reintento con la misma Idempotency-Key (no se ha cobrado ni lanzado nada)"}},"required":["data","idempotente"]},"AnalisisDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","processing","completed","failed"]},"licitacionId":{"type":"string","format":"uuid","nullable":true},"fileName":{"type":"string","description":"Nombre del pliego o de la licitación analizada"},"resumenEjecutivo":{"type":"string","nullable":true},"objetoContrato":{"type":"string","nullable":true},"requisitosSolvencia":{"type":"object","additionalProperties":true,"nullable":true,"description":"economica (facturación, seguro RC, patrimonio) y tecnica (experiencia, certificaciones exigidas a la empresa, certificacionesProducto, otrosRequisitos)"},"criteriosAdjudicacion":{"type":"array","items":{"type":"object","additionalProperties":true},"nullable":true,"description":"Criterios con puntos (suman 100) y tipo fórmula / juicio de valor"},"plazosClave":{"type":"array","items":{"type":"object","additionalProperties":true},"nullable":true,"description":"nombre, fecha, descripcion"},"penalizaciones":{"type":"array","items":{"type":"object","additionalProperties":true},"nullable":true},"clausulasRiesgo":{"type":"array","items":{"type":"object","additionalProperties":true},"nullable":true},"elegibilidad":{"type":"object","additionalProperties":true,"nullable":true,"description":"Evaluación del modelo SIN acceso al perfil de empresa (cumple: null en lo que dependa del perfil). El veredicto contra el perfil es /v1/licitaciones/{id}/veredicto."},"recomendaciones":{"nullable":true,"type":"array","items":{"type":"string"}},"documentosRequeridos":{"type":"array","items":{"type":"object","additionalProperties":true},"nullable":true,"description":"Documentos que exige el pliego (nombre, obligatorio, sobre)"},"entregables":{"type":"array","items":{"type":"object","additionalProperties":true},"nullable":true},"requisitosMinimos":{"nullable":true,"type":"array","items":{"type":"string"}},"garantias":{"type":"object","additionalProperties":true,"nullable":true,"description":"provisional, definitiva y complementaria: exigida, importe, descripcion"},"cpvCodes":{"nullable":true,"description":"CPV que declara el pliego, 8 dígitos","type":"array","items":{"type":"string"}},"cobertura":{"type":"object","additionalProperties":true,"nullable":true,"description":"Qué páginas de qué documentos se analizaron (recortado, documentos[])"},"lotes":{"type":"array","items":{"type":"object","additionalProperties":true},"nullable":true},"creditsUsed":{"type":"number","description":"Créditos cobrados por este análisis"},"modelVersion":{"type":"string","nullable":true,"description":"Versión del esquema de extracción (cambia cuando cambia la forma)"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"_restricted":{"type":"boolean","description":"true si el plan o derecho no cubre la evaluación: el cuerpo viene recortado y _message lo explica"},"_message":{"type":"string"}},"required":["id","status","licitacionId","fileName","resumenEjecutivo","objetoContrato","requisitosSolvencia","criteriosAdjudicacion","plazosClave","penalizaciones","clausulasRiesgo","elegibilidad","recomendaciones","documentosRequeridos","entregables","requisitosMinimos","garantias","cpvCodes","cobertura","lotes","creditsUsed","modelVersion","createdAt","updatedAt"]},"AnalisisRespuestaDto":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AnalisisDto"}},"required":["data"]},"EstadoAnalisisRespuestaDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","processing","completed","failed"]},"etapa":{"type":"string","nullable":true,"enum":["descargando","leyendo","analizando"],"description":"Etapa real del worker mientras corre; null si no"},"iniciadoEn":{"type":"string","format":"date-time","nullable":true,"description":"Desde cuándo corre; null si no está en curso"},"errorMessage":{"type":"string","description":"Solo en status failed y si el motivo es presentable"}},"required":["id","status","etapa","iniciadoEn"]},"VeredictoMetaDto":{"type":"object","properties":{"licitacionId":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid","description":"Empresa contra la que se evaluó: la activa del dueño de la clave (o la por defecto de la organización)"},"calculatedAt":{"type":"string","format":"date-time","description":"Cuándo se calculó"},"stale":{"type":"boolean","description":"true si el perfil de empresa cambió, desde el cálculo, en algún campo que el motor consulta: conviene recalcular (POST)"}},"required":["licitacionId","companyId","calculatedAt","stale"]},"SiguientePasoDto":{"type":"object","properties":{"accion":{"type":"string","enum":["analizar_pliegos","calcular_veredicto"],"description":"Qué hacer para obtener (o mejorar) el veredicto"},"metodo":{"type":"string","enum":["GET","POST"]},"ruta":{"type":"string","description":"Ruta relativa a la API"},"body":{"type":"object","additionalProperties":true},"scope":{"type":"string","description":"Scope que necesita la clave"},"motivo":{"type":"string","description":"Por qué, en una frase"}},"required":["accion","metodo","ruta","motivo"]},"VeredictoRespuestaDto":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true,"description":"El veredicto: decision (GO | NO_GO | REVIEW), score 0-100, checks (cada requisito con cumple true/false/null, razonNull, importancia, naturaleza), summary, missingProfileFields, missingProfileKeys, ambiguousRequirements. La misma estructura que la ficha web."},"meta":{"$ref":"#/components/schemas/VeredictoMetaDto"},"siguientePaso":{"description":"Solo cuando el veredicto no ha podido evaluar nada (0 checks): cómo desbloquearlo","allOf":[{"$ref":"#/components/schemas/SiguientePasoDto"}]}},"required":["data","meta"]}}},"security":[{"x-api-key":[]}]}