Documentación

API de Wolovy

Lee clientes, proyectos y horas desde tus sistemas y publica informes en el Portal del Cliente con una API key de empresa.

URL base
https://app.wolovy.com/api
Autenticación
X-Company-Token
Permisos
Lectura + 5 escrituras
Límite
200 peticiones por minuto

Primeros pasos

La API de Wolovy te deja leer tus clientes, proyectos y horas desde otros sistemas, mantener el bloque de mantenimiento de cada proyecto y publicar informes en el Portal del Cliente. Funciona con una API key de empresa.

  1. Crea una API key. En Wolovy, entra en Herramientas de empresa (/company/tools) y abre la pestaña API keys. Solo pueden hacerlo los roles super_admin y admin.
  2. Envíala en cada petición con la cabecera X-Company-Token.
  3. Llama a la API desde tu servidor, no desde el navegador: la API no acepta peticiones de orígenes web que no estén autorizados.
curl "https://app.wolovy.com/api/clients?companyId=<COMPANY_ID>&page=1&pageSize=20" \
  -H "X-Company-Token: <TOKEN>"

Autenticación

Cada empresa puede tener hasta 20 API keys, cada una con su etiqueta para saber qué aplicación la usa. Todas son válidas a la vez y se revocan por separado. Regenerar o revocar una key invalida su valor anterior al momento.

  • La key va siempre en la cabecera X-Company-Token: <TOKEN>.
  • Authorization: Bearer es para las sesiones de usuario y no sirve para las API keys.
  • Cada key pertenece a una empresa. Si envías un companyId (en la query) o una cabecera X-Company-Id de otra empresa, la respuesta es 403 api_token_company_denied.

Trata cada key como una contraseña: una por integración, guardada en el servidor y nunca en el código de una web o una app.

Qué puede hacer una key

Las API keys son de solo lectura: aceptan GET, HEAD y OPTIONS. Solo hay cinco escrituras permitidas:

Método Ruta Para qué
PATCH /projects/{id}/maintenance Mantener el bloque de mantenimiento de un proyecto
POST /client-portals/{id}/entries Crear un informe en un Portal del Cliente
PATCH /client-portals/{id}/entries/reorder Reordenar los informes
PATCH /client-portals/{id}/entries/{entryId} Editar un informe
POST /client-portals/{id}/entries/{entryId}/publish Publicar un informe

Cualquier otra escritura responde 403 api_token_read_only.

Tampoco están disponibles con una key, ni para leer: Project Management (/pm), Tickets, Salud económica, la gestión de API keys y las lecturas del Portal del Cliente. Todo eso necesita una sesión de usuario.

Convenciones

  • URL base: https://app.wolovy.com/api
  • Formato: JSON. En las escrituras, envía Content-Type: application/json.
  • Fechas: ISO 8601. YYYY-MM-DD en los filtros y YYYY-MM-DDTHH:mm:ss.sssZ en las marcas de tiempo.
  • Paginación: page y pageSize. Las listas paginadas devuelven { total, items, page, pageSize }.
  • Parámetros: envía solo los que están documentados.

Límite de peticiones

Hasta 200 peticiones por minuto por dirección IP. Cada respuesta lleva x-ratelimit-limit, x-ratelimit-remaining y x-ratelimit-reset. Si te pasas, la respuesta es 429 con una cabecera retry-after que indica cuándo volver a intentarlo.

Errores

Los errores de la aplicación llegan como { "message": "…", "code": "…" } (code no siempre está). Los del servidor, como el 429 o un JSON mal formado, llegan como { "statusCode", "error", "message" }.

Código Cuándo
400 Cuerpo o parámetros no válidos. Por ejemplo, api_token_user_id_required.
401 Falta la key o no es válida.
403 api_token_read_only, api_token_company_denied, api_token_not_allowed o api_token_user_denied.
404 El recurso no existe.
413 El HTML de un informe pasa de 512 KB.
422 Un componente de un informe no es válido. La respuesta incluye issues.
429 Límite de peticiones superado.

Clientes

Listar clientes

GET /clients?companyId={uuid}&page=1&pageSize=20&search=

Lista paginada de los clientes de la empresa. Respuesta: { total, items, page, pageSize }.

curl "https://app.wolovy.com/api/clients?companyId=<COMPANY_ID>&search=brava" \
  -H "X-Company-Token: <TOKEN>"

Ver un cliente

GET /clients/{id}

Detalle del cliente, con sus notas.

Proyectos

Listar proyectos

GET /projects?companyId={uuid}&page=1&pageSize=20&search=&status=&priority=&clientId=&maintenance=&dateRange=&from=&to=

Lista y filtra los proyectos de la empresa.

Ver un proyecto

GET /projects/{id}

Detalle completo: cliente, asignaciones, tiempos y subproyectos. Incluye durationBreakdown: { direct, subtasks, projectManagement } con las horas imputadas directamente al proyecto, a sus trabajos (subtasks en la API) y a sus tareas de Project Management. totalDurationSeconds las suma todas.

Subproyectos

GET /projects/{id}/subprojects

Actualizar el mantenimiento

PATCH /projects/{id}/maintenance

Actualiza solo el bloque de mantenimiento del proyecto. Es una de las cinco escrituras permitidas con API key. Acepta un cuerpo parcial con cualquiera de estos campos: maintenance (booleano), maintenanceService y un campo por mes, de maintenanceJanuary a maintenanceDecember.

  • Solo se escriben las claves que envías; las demás se quedan como estaban.
  • null o una cadena vacía borran el contenido de ese campo.
  • Cualquier otro campo del proyecto, o un cuerpo vacío, responde 400.
  • Respuesta: { applied: string[], serverValues: {…}, updatedAt }.

Este ejemplo solo toca enero y marzo; los otros diez meses y maintenanceService se quedan igual:

curl -X PATCH "https://app.wolovy.com/api/projects/<PROJECT_ID>/maintenance" \
  -H "Content-Type: application/json" \
  -H "X-Company-Token: <TOKEN>" \
  -d '{"maintenanceJanuary":"Revisión anual\n12 - Cambio de filtros","maintenanceMarch":null}'

Y este marca un proyecto como de mantenimiento:

curl -X PATCH "https://app.wolovy.com/api/projects/<PROJECT_ID>/maintenance" \
  -H "Content-Type: application/json" \
  -H "X-Company-Token: <TOKEN>" \
  -d '{"maintenance":true,"maintenanceService":"Contrato anual de soporte"}'

Tiempo

GET /time/my?userId={uuid}&date=YYYY-MM-DD
GET /time/my?userId={uuid}&from=YYYY-MM-DD&to=YYYY-MM-DD

La línea de tiempo de una persona, de un día o de un rango de fechas. Con una API key, userId es obligatorio (si falta: 400 api_token_user_id_required) y tiene que ser de la empresa de la key (si no: 403 api_token_user_denied). Puedes sacar los userId de las asignaciones que devuelve GET /projects/{id}.

Estadísticas

GET /stats/dedication?companyId={uuid}&month=1-12&year=YYYY

Devuelve un summary con las horas totales, las personas y los proyectos del mes indicado. companyId, month y year son obligatorios.

curl "https://app.wolovy.com/api/stats/dedication?companyId=<COMPANY_ID>&month=9&year=2026" \
  -H "X-Company-Token: <TOKEN>"

Portal del Cliente

Con una API key puedes crear, editar, ordenar y publicar informes en el Portal del Cliente. No puedes leer los portales: el identificador del portal lo copias de Wolovy, de la dirección de su ficha (/client-portals/{id}).

Método Ruta Cuerpo
POST /client-portals/{id}/entries { title, html?, publish? }
PATCH /client-portals/{id}/entries/reorder { orderedIds: [uuid] }
PATCH /client-portals/{id}/entries/{entryId} { title?, html?, status? }
POST /client-portals/{id}/entries/{entryId}/publish { note? }

POST /entries responde { entry, report, componentCount }. Guarda el entry.id: es el que usan los demás endpoints.

Un informe creado por API queda siempre en borrador, salvo que envíes publish: true o llames al endpoint de publicación. Cada versión creada así queda marcada como creada por API.

curl -X POST "https://app.wolovy.com/api/client-portals/<PORTAL_ID>/entries" \
  -H "Content-Type: application/json" \
  -H "X-Company-Token: <TOKEN>" \
  -d '{"title":"Septiembre 2026","html":"<h1>Septiembre</h1><p>Este mes hemos publicado la nueva web.</p>"}'

HTML permitido

El HTML se limpia al guardarlo y solo se sirve la copia limpia. Además, se muestra dentro de un iframe aislado, sin cookies, sin almacenamiento y sin red.

  • Etiquetas permitidas: p br hr h1…h6 strong b em i u s small sub sup mark code pre blockquote abbr time del ins ul ol li dl dt dd table thead tbody tfoot tr th td caption colgroup col figure figcaption img a div span section article header footer main aside.
  • Se eliminan con su contenido: script style noscript template textarea option title iframe object embed applet param base link meta form input button select label svg math canvas audio video source track slot portal marquee frame frameset dialog details summary.
  • Atributos: class id title lang dir role style aria-*. Las clases solo se conservan si empiezan por wv-.
  • Enlaces: solo https: y mailto:. Siempre se abren en otra pestaña, con rel="noopener noreferrer nofollow ugc".
  • Imágenes: solo data:image/… o imágenes subidas al propio portal desde Wolovy. Las imágenes externas https:// se eliminan.
  • Estilos en línea: una lista cerrada de propiedades (color, fondo, tipografía, espaciado, bordes, flex y grid). Se rechaza cualquier valor con url(, expression(, @import, \, < o /*.
  • Nunca se ejecuta JavaScript del contenido, ni atributos como onclick u onload.
  • Límites: 512 KB por documento, 5.000 elementos, 64 niveles de anidamiento y 40 componentes.

Componentes

El contenido nunca ejecuta JavaScript, así que un gráfico se declara como un elemento vacío con un único atributo JSON, y Wolovy lo dibuja. Solo div, section y figure pueden llevar un componente.

<div data-chart='{"type":"bar","title":"Visitas","categories":["Jun","Jul","Ago","Sep"],
  "series":[{"name":"Sesiones","data":[1200,1500,1800,2200]}]}'></div>

<div data-counter='{"label":"Conversiones","value":1234,"suffix":" uds"}'></div>

<div data-progress='{"label":"Avance","value":72}'></div>

<div data-kpi='{"columns":3,"items":[
  {"label":"Visitas","value":18400},
  {"label":"Conversiones","value":312},
  {"label":"Ingresos","value":9450,"prefix":"EUR "}]}'></div>

<div data-comparison='{"aLabel":"2025","bLabel":"2026","rows":[
  {"label":"Sesiones","a":14000,"b":18400},
  {"label":"Rebote (%)","a":58,"b":47,"higherIsBetter":false}]}'></div>

El JSON se valida al guardar: una clave desconocida es un error 422, no se ignora, así que una errata sale como error y no como un gráfico vacío. Máximo 8 KB de JSON por componente y 40 componentes por informe.

data-chart

Campo Tipo Notas
type bar, stacked-bar, line, area o donut Obligatorio
title Texto de hasta 80 caracteres
unit Texto de hasta 12 caracteres Se muestra en los tooltips
decimals De 0 a 4, por defecto 0
legend Booleano, por defecto true
categories De 1 a 48 textos de hasta 32 caracteres El eje X
series De 1 a 6 { name, color?, data: number[] }

Cada series[].data debe tener exactamente tantos valores como categories. Un donut admite una sola serie y su leyenda muestra las categorías.

data-counter

Campo Tipo Notas
label Texto de 1 a 48 caracteres Obligatorio
value Número Obligatorio
prefix Texto de hasta 4 caracteres Respeta los espacios: "EUR " da EUR 9.450
suffix Texto de hasta 8 caracteres Igual: " uds" da 128 uds
decimals De 0 a 4, por defecto 0
animate Booleano, por defecto true Respeta la preferencia de movimiento reducido
tone Ver colores

data-progress

Campo Tipo Notas
value Número Obligatorio
max Número mayor que 0, por defecto 100
label Texto de hasta 48 caracteres
showValue Booleano, por defecto true Muestra el porcentaje
tone Ver colores

data-kpi

Campo Tipo Notas
columns 2, 3 o 4 A partir de 640 px de ancho; por debajo, la rejilla se adapta
items De 1 a 8 objetos con la forma de data-counter Obligatorio

data-comparison

Campo Tipo Notas
title Texto de hasta 120 caracteres
aLabel / bLabel Texto de 1 a 24 caracteres Obligatorios, encabezan las dos columnas
format number, percent o currency, por defecto number
currency Código de 3 letras Solo con format: "currency"
decimals De 0 a 4, por defecto 0
unit Texto de hasta 12 caracteres
higherIsBetter Booleano, por defecto true Se puede fijar fila a fila
rows De 1 a 24 { label, a, b, higherIsBetter? }

La columna Δ se pinta en verde cuando el cambio es a mejor y en rojo cuando es a peor. higherIsBetter se fija por fila porque un informe mezcla métricas donde subir es bueno (sesiones) con otras donde lo bueno es bajar (rebote, tiempo de carga).

Colores

tone y color solo aceptan primary, success, warning, danger, muted o series. Los colores reales salen de la configuración de la empresa, nunca del HTML: así todos los informes son coherentes y un contenido generado por IA no puede romper la identidad visual.

Informes generados con IA

  1. Copia el identificador del portal desde su ficha en Wolovy.
  2. Lee con la API los datos que necesite el informe: GET /projects/{id}, GET /time/my, GET /stats/dedication.
  3. Crea el informe con POST /client-portals/{id}/entries. Queda en borrador.
  4. Alguien de la agencia lo revisa, lo publica y envía el aviso al cliente desde Wolovy. Si confías en tu proceso, también puedes publicarlo por API.

Nada se publica solo por haberlo generado una IA.

Incrustar el portal

Cada portal tiene también un enlace para incrustar: el dashboard y los informes, de solo lectura y sin tickets. Se genera en la ficha del portal, que solo funciona en los dominios que autorices.

<iframe src="https://app.wolovy.com/api/client-portal/e/<EMBED_TOKEN>"
        style="width:100%;min-height:800px;border:0"
        loading="lazy" referrerpolicy="no-referrer"></iframe>

Buenas prácticas

  • Crea una key por integración, con una etiqueta que diga cuál es.
  • Guárdala en el servidor. Nunca en el código de una web, de una app o de un repositorio.
  • Si sospechas que se ha filtrado, regenérala y actualiza la integración.
  • Envía siempre companyId explícito, sobre todo si tu cuenta lleva varias empresas.
  • Respeta el límite de 200 peticiones por minuto y, ante un 429, espera lo que indique retry-after.

¿Aún sin cuenta? Pruébalo con tu equipo.

Empezar

30 días, sin tarjeta. Después, 6 € por persona y mes + IVA.