Solo lectura, a propósito
La API devuelve lo que ya ves en la aplicación, leído con las mismas
consultas: no hay un modelo de datos paralelo que se pueda desfasar.
Tampoco hay endpoints de escritura, y no es una carencia temporal: nada
en Licia presenta documentación en tu nombre ante un organismo, y una API
de escritura convertiría nuestras tablas internas en el contrato de
integración de otro. Una petición POST,
PUT o DELETE
responde 405 diciendo justo esto.
Todo el contenido generado por IA requiere revisión humana antes de presentarse. Los veredictos y los borradores que devuelve esta API son apoyo a una decisión, no la decisión.
Autenticación
Un token por empresa, creado desde
Perfil de empresa → API. Se muestra una única vez al
crearlo: guardamos solo su hash, así que si se pierde no se puede
recuperar —se crea otro y se revoca el anterior—. Empieza por
licia_ y viaja en la cabecera
Authorization:
curl -H "Authorization: Bearer licia_…" \
https://licia.es/api/v1/licitaciones
El token autoriza exactamente una empresa: la suya. Quien puede crearlo o
revocarlo es quien puede configurar esa empresa, la misma regla que
gobierna el resto de /perfil. Revocar surte
efecto en la petición siguiente.
Un token lee lo mismo que lee la cuenta que lo creó, y solo mientras esa
cuenta siga teniendo acceso a la empresa: si a esa persona se le retira el
sitio en el equipo, el token deja de funcionar con ella
(401 acceso_revocado). Para una integración
que tiene que sobrevivir a la salida de alguien, créala desde la cuenta
propietaria.
Trátalo como una contraseña: cualquiera que lo tenga puede leer tus licitaciones, tus veredictos y tu embudo. No lo pongas en una URL ni en un repositorio.
Límite de peticiones
60 peticiones por minuto y por token. Al pasarlo, la
respuesta es 429 con la cabecera
Retry-After en segundos. Las páginas admiten
hasta 100 resultados
(limite) y se recorren con
pagina; cada respuesta trae
total y paginas.
Errores
Siempre JSON, con la misma forma:
{"error": {"codigo": "token_invalido", "mensaje": "Ese token no es válido o ha sido revocado."}}
401 token ausente, desconocido o revocado —los
tres con el mismo mensaje, para no confirmar si una cadena filtrada fue
alguna vez válida—, más acceso_revocado, que sí
se distingue porque para verlo hay que tener ya un token válido;
400 parámetro no
reconocido; 404 recurso inexistente;
405 escritura; 429
límite de peticiones.
Fechas y horas
Todas en ISO 8601 y todas con zona: nunca hay que suponerla. Hay dos tipos, y llevan zonas distintas a propósito.
plazo es el cierre del
procedimiento, tal y como lo publica el órgano de contratación:
una hora de pared española, en Europe/Madrid
(+01:00 o +02:00
según la fecha). Es la misma hora que se ve en la ficha de la licitación
y la misma que viaja en el calendario .ics.
La PLACSP publica estas horas sin zona, así que la fijamos
nosotros al ingerir y no al servirla —convertirla dos veces es
exactamente como un plazo se mueve una hora—.
El resto (publicado,
veredicto_fecha,
creado, enviado…)
son instantes nuestros y van en UTC.
{"plazo": "2026-09-14T14:00:00+02:00", "publicado": "2026-09-01T07:12:44+00:00"}
Endpoints
Esta lista se genera del esquema OpenAPI de la propia aplicación: si un endpoint existe, está aquí.
GET /api/v1/avisos
Los avisos de la empresa
La misma bandeja que /avisos, y por tanto exactamente los eventos que el webhook envía: es lo que permite recuperar lo ocurrido mientras un endpoint estaba caído, sin que la aplicación y la integración puedan contar historias distintas.
-
limite— Cuántos avisos devolver (máximo 100).
GET /api/v1/crm/deals
Oportunidades sincronizadas en CRM
Lista de oportunidades/deals creados y sincronizados en HubSpot, Pipedrive o Salesforce para las licitaciones de esta empresa.
GET /api/v1/embudo
El embudo de la empresa
El tablero del panel: cuántas licitaciones hay en cada fase y cuáles son, con la decisión del equipo y su fecha. Las fases y sus nombres son los mismos que usa la aplicación.
GET /api/v1/licitaciones
Licitaciones de la empresa
El listado que ve la empresa, con su veredicto, su fase del embudo y su borrador si lo hay. Sin filtros devuelve la convocatoria abierta de los CPV vigilados (lo que muestra /licitaciones). Con `analizadas=1` devuelve las que ya tienen veredicto, sin restringir por CPV, y con `seguidas=1` las que están en el embudo: entre los tres, el panel se reconstruye entero.
-
estado— `abiertas` (por defecto) o `todas`, que incluye las cerradas. -
analizadas— `1` para devolver solo las que tienen veredicto. -
seguidas— `1` para devolver solo las del embudo. -
veredicto— Filtra por `go`, `no_go` o `needs_human_check`. -
etapa— Filtra por fase del embudo. -
cpv— Prefijo CPV. Varios, separados por comas (`45,72`): una licitación vale si encaja con cualquiera de ellos. -
busqueda— Texto en título, resumen o expediente. -
pagina— Página, desde 1. -
limite— Resultados por página (máximo 100).
GET /api/v1/licitaciones/{tender_id}
Análisis de una licitación
La ficha completa para esta empresa: la licitación, los requisitos extraídos del pliego con sus citas textuales, el veredicto Go/No-Go con su razonamiento y la fase del embudo. Las citas son el texto congelado en el momento de la extracción, con el identificador del fragmento: un veredicto sin su cita es una opinión.
-
tender_id(obligatorio)
GET /api/v1/perfil
La empresa del token
Identifica la empresa a la que pertenece el token y devuelve los contadores del panel: licitaciones indexadas, novedades de los últimos 7 días en los CPV vigilados, veredictos GO vigentes, borradores pendientes y total del embudo.
Reconstruir el panel
El panel de la aplicación no usa nada que no esté aquí. Con tres llamadas se rehace entero:
-
/api/v1/perfil— la tira de contadores: licitaciones indexadas, novedades de la semana en tus CPV, veredictos GO vigentes, borradores pendientes y total del embudo. -
/api/v1/licitaciones?analizadas=1— la lista de trabajo. Cada fila trae suveredictoy suborrador, que es lo que la agrupa en «revisar», «genera el borrador» y «borrador generado». -
/api/v1/licitacionessin filtros — el radar: la convocatoria abierta de los CPV que vigilas. Las que llegan converedictoyetapaanullson las que nadie ha mirado todavía. -
/api/v1/embudo— el tablero por fases, con la decisión del equipo y desde cuándo está en esa fase.
Lo que no viaja: el estado de ninguna otra empresa. Ni siquiera el aviso anónimo de «este lote ya está reservado» que la aplicación muestra en pantalla —una respuesta JSON se reenvía como se reenvía un fichero, y una pantalla no—. Solo tus propias ofertas.
Webhooks firmados
Un endpoint HTTPS por empresa, configurado en Perfil de empresa → API. Recibe los mismos avisos que el correo y los canales de chat, porque son las mismas filas: si un aviso está en «Solo en la app», tampoco se envía por webhook. Los eventos salen del servidor de análisis, no de la web.
Tipos de evento:
new_match— Licitaciones nuevas que encajandraft_ready— Borradores que escribe el autopilotodeadline— Plazos que se acercanexpiry— Documentos que caducanoutcome— Resultados publicadosjob_done— Análisis terminadossaved_search— Coincidencias de tus búsquedas guardadasgo_verdict— Veredicto GO comercialstage_changed— Cambio de fase en el embudo
Cuerpo de la petición (POST, JSON):
{
"id": 1843,
"tipo": "deadline",
"creado": "2026-08-31T09:00:00+00:00",
"empresa": {"id": 7, "nombre": "ACME Servicios SL"},
"titulo": "Quedan 3 días: Servicios de mantenimiento",
"cuerpo": "Plazo de presentación: 03/09/2026 (3 días).",
"enlace": "https://licia.es/licitaciones/912",
"licitacion": {"id": 912, "expediente": "2026/PA/12", "titulo": "…",
"api": "https://licia.es/api/v1/licitaciones/912"}
}
Cabeceras:
X-Licia-Event— el tipo de evento.X-Licia-Delivery— el identificador del aviso; el mismo en cada reintento, para que puedas descartar duplicados.X-Licia-Attempt— número de intento.X-Licia-Timestamp— el instante de la firma, en segundos Unix.X-Licia-Signature—t=<timestamp>,v1=<hmac>.
Para verificar: calcula
HMAC-SHA256(secreto, "<timestamp>." + cuerpo_tal_cual)
en hexadecimal y compáralo con v1
usando una comparación en tiempo constante. Comprueba también que el
timestamp es reciente (unos cinco minutos): va dentro de lo firmado
precisamente para que una copia de un evento antiguo no se pueda reenviar
como nueva. Firma sobre el cuerpo en bruto, antes de parsear el
JSON: cualquier reserialización cambia los bytes y por tanto la firma.
import hashlib, hmac, time
def valido(secreto, cabecera, cuerpo_bytes, tolerancia=300):
partes = dict(p.split("=", 1) for p in cabecera.split(",") if "=" in p)
t, firma = int(partes["t"]), partes["v1"]
if abs(time.time() - t) > tolerancia:
return False
esperada = hmac.new(secreto.encode(),
f"{t}.".encode() + cuerpo_bytes,
hashlib.sha256).hexdigest()
return hmac.compare_digest(esperada, firma)
El secreto se muestra una sola vez, al conectar el webhook o al rotarlo, y es un credencial: no lo registres en logs. Rotarlo invalida las firmas posteriores, no las ya entregadas.
Reintentos y fallos. Una respuesta
2xx cierra la entrega. Un fallo temporal
(tiempo de espera, 5xx,
429) se reintenta hasta
3 veces con espera creciente. Un rechazo definitivo
(400, 401,
403, 404,
410) falla al primer intento y lo verás en el
aviso, en /avisos, junto al resto de canales: cada
canal lleva su propia cuenta, así que un endpoint caído nunca te cuesta el
correo del mismo aviso.
Qué no hacemos. No seguimos redirecciones —configura la URL
final—, no aceptamos http://, no enviamos a
direcciones que no sean públicas de internet (ni por nombre ni por
redirección de DNS) y leemos como mucho unos pocos kilobytes de tu
respuesta. Basta con que devuelvas 200 y un
cuerpo vacío.
Si tu endpoint estuvo caído.
/api/v1/avisos devuelve la misma bandeja
que la aplicación —las mismas filas que el webhook envía—, así que se
recupera lo ocurrido sin que la integración y la pantalla puedan contar
historias distintas.
Conectores CRM (HubSpot, Pipedrive, Salesforce)
Licia sincroniza automáticamente tus oportunidades comerciales con tu CRM sin necesidad de desarrollo adicional:
-
Veredicto GO: cuando una licitación encaja y recibe un veredicto viable
(
go_verdict), se crea automáticamente la oportunidad (Deal / Opportunity) en tu CRM con el título del expediente, importe estimado, fecha límite de presentación y enlace a Licia. - Cambios de fase: cuando mueves la licitación en el embudo (de evaluación a preparación, presentada, adjudicada o descartada), el conector actualiza la etapa del deal en tu CRM.
-
Seguridad y asientos: la integración comprueba en cada entrega que el usuario que
la configuró mantiene su asiento en la empresa (
_creator_has_access).
Puedes conectar tu CRM directamente desde Perfil → API y CRM.
Las oportunidades creadas se pueden consultar mediante la API en
/api/v1/crm/deals.
Superficie Zapier y Make
Para conectar Licia con cualquier otra herramienta (Notion, Airtable, Monday, Asana, Google Sheets o tu propio CRM), utiliza la combinación de Webhooks salientes y la API REST v1:
- Disparador (Trigger): Crea un webhook en Zapier («Webhooks by Zapier → Catch Hook») o en Make («Custom Webhook») y copia su URL HTTPS en el apartado Webhook de eventos de tu perfil.
-
Filtro de evento: Filtra por la propiedad
tipo:go_verdict: para crear oportunidades cuando un pliego es viable.stage_changed: para sincronizar cambios de fase en tu gestor de proyectos.deadline: para enviar alertas urgentes a canales específicos.
-
Acciones complementarias (Actions): Utiliza una petición HTTP con cabecera
Authorization: Bearer <tu_token_api>hacia/api/v1/licitaciones/{id}para consultar el desglose de requisitos, criterios de adjudicación o resumen ejecutivo.