Files
4ventures-client-portal/docs/API_INTEGRACIONES.md
4Ventures 1cf8e7c1bd Require 9:16 and 1.91:1 adaptations alongside every static 4:5 ad creative.
Generation briefs and Canva webhooks now declare all three Meta placements so WF3/WF5 deliver feed, Stories, and landscape from one Freepik base.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-13 17:03:40 +02:00

371 lines
11 KiB
Markdown

# Especificación y Contrato de API — Integraciones n8n y Claude
Esta documentación describe todos los endpoints disponibles en `https://app.4ventures.es/api/integrations/...` para la ingesta de métricas de Meta Ads, detección de fatiga, relevo creativo con Claude, aprobaciones y publicación.
---
## 1. Autenticación y Seguridad
Todas las peticiones a la API de integraciones deben incluir la cabecera `X-Api-Key`:
- **Para n8n:** Clave asignada en `N8N_API_KEY`
- **Para Claude:** Clave asignada en `CLAUDE_API_KEY`
```http
X-Api-Key: <TU_API_KEY>
Content-Type: application/json
```
### Códigos de estado HTTP globales
- `200 OK`: Petición procesada correctamente.
- `400 Bad Request`: Parámetros obligatorios faltantes o payload JSON malformado.
- `401 Unauthorized`: Cabecera `X-Api-Key` ausente o no válida.
- `403 Forbidden`: El cliente (`client_id` / `tenant_id`) no tiene activo el flag `creatives_enabled = true`.
- `404 Not Found`: El cliente o recurso solicitado no existe.
- `500 Internal Server Error`: Error inesperado en el servidor.
---
## 2. Endpoints para n8n (Ingesta, Fatiga y Publicación)
### 2.1 Obtener Clientes Activos
Obtiene la lista de clientes que tienen activado el módulo de creatividades y su cuenta de anuncios de Meta.
- **Método:** `GET`
- **Ruta:** `/api/integrations/clients?creatives_enabled=true`
- **Headers:** `X-Api-Key: <N8N_API_KEY>`
#### Respuesta de ejemplo (`200 OK`):
```json
[
{
"id": "8bec977a-6032-4759-aa7f-b5042081f450",
"name": "Blue Parrot School",
"slug": "bps",
"meta_ad_account_id": "act_1052328112282270",
"slack_channel_id": null,
"drive_folder_id": null,
"creatives_enabled": true,
"client_id": "8bec977a-6032-4759-aa7f-b5042081f450",
"tenant_id": "8bec977a-6032-4759-aa7f-b5042081f450"
}
]
```
---
### 2.2 Ingesta Diaria de Métricas (Bulk Upsert)
Inserta o actualiza las métricas por anuncio y fecha. Si el anuncio (`meta_ad_id`) no existía en la base de datos, lo crea automáticamente en estado `publicada`.
- **Método:** `POST`
- **Ruta:** `/api/integrations/metrics/daily`
- **Headers:** `X-Api-Key: <N8N_API_KEY>`, `Content-Type: application/json`
#### Payload de ejemplo (`Request Body`):
```json
{
"client_id": "8bec977a-6032-4759-aa7f-b5042081f450",
"metrics": [
{
"meta_ad_id": "1202058493829102",
"date": "2026-10-11",
"campaign_name": "Matrículas Octubre 2026",
"impressions": 4200,
"reach": 3800,
"clicks": 135,
"spend": 54.20,
"results": 6,
"raw": {
"cpm": 12.90,
"ctr": 3.21
}
}
]
}
```
#### Respuesta de ejemplo (`200 OK`):
```json
{
"ok": true,
"count": 1,
"rows": [
{
"id": "7e417520-6089-4cc8-9e44-f08b4e948b2a",
"meta_ad_id": "1202058493829102",
"date": "2026-10-11"
}
]
}
```
---
### 2.3 Obtener Candidatos de Fatiga
Devuelve las creatividades actualmente publicadas con sus métricas dentro de la ventana de análisis (`lookback_days`) y las reglas de fatiga del cliente.
- **Método:** `GET`
- **Ruta:** `/api/integrations/fatigue/candidates?client_id=8bec977a-6032-4759-aa7f-b5042081f450`
- **Headers:** `X-Api-Key: <N8N_API_KEY>`
#### Respuesta de ejemplo (`200 OK`):
```json
{
"client_id": "8bec977a-6032-4759-aa7f-b5042081f450",
"tenant_id": "8bec977a-6032-4759-aa7f-b5042081f450",
"lookback_days": 7,
"rules": {
"lookback_days": 7,
"min_impressions": 2000,
"max_frequency": 3.0,
"ctr_drop_pct": 30.0,
"cpa_rise_pct": 25.0,
"signals_required": 2,
"auto_request_relief": true
},
"candidates": [
{
"id": "c05d4143-f19d-421a-b8d9-9b03d6b0889a",
"meta_ad_id": "1202058493829102",
"format": "estatico",
"status": "publicada",
"metrics": [
{
"date": "2026-10-10",
"impressions": 3000,
"clicks": 90,
"spend": 45.0,
"results": 5
}
]
}
]
}
```
---
### 2.4 Marcar Creatividad Fatigada
Cambia el estado de la creatividad a `fatigada`, guarda el evento de auditoría y, si `auto_request_relief` está activo, crea automáticamente una petición de relevo en `generation_requests`.
- **Método:** `POST`
- **Ruta:** `/api/integrations/fatigue/mark`
- **Headers:** `X-Api-Key: <N8N_API_KEY>`, `Content-Type: application/json`
#### Payload de ejemplo (`Request Body`):
```json
{
"client_id": "8bec977a-6032-4759-aa7f-b5042081f450",
"creative_id": "c05d4143-f19d-421a-b8d9-9b03d6b0889a",
"signals": {
"ctr_drop_pct": 32.5,
"frequency": 3.4
}
}
```
#### Respuesta de ejemplo (`200 OK`):
```json
{
"ok": true,
"creative_id": "c05d4143-f19d-421a-b8d9-9b03d6b0889a",
"status": "fatigada",
"signals": { "ctr_drop_pct": 32.5, "frequency": 3.4 },
"generation_request": {
"id": "377cf30e-81e5-4d80-8f4f-f30a8dd2d239",
"status": "pendiente",
"reason": "fatiga",
"brief": {
"client_slug": "bps",
"variants_requested": 2,
"avoid_angles": ["objecion_precio"],
"format": "estatico",
"aspect_ratio": "4:5",
"aspect_ratios": ["4:5", "9:16", "1.91:1"],
"required_placements": [
{ "aspect_ratio": "4:5", "role": "feed", "size": "1080x1350", "meta_key": "asset_url" },
{ "aspect_ratio": "9:16", "role": "stories", "size": "1080x1920", "meta_key": "stories_asset_url", "canva_template_key": "stories_9_16" },
{ "aspect_ratio": "1.91:1", "role": "landscape", "size": "1200x628", "meta_key": "landscape_asset_url", "canva_template_key": "landscape_1_91" }
]
}
}
}
```
---
### 2.5 Listar Creatividades Aprobadas para Publicar
Permite a n8n consultar las creatividades aprobadas pendientes de subirse a Meta Ads.
- **Método:** `GET`
- **Ruta:** `/api/integrations/creatives?status=aprobada&client_id=8bec977a-6032-4759-aa7f-b5042081f450`
- **Headers:** `X-Api-Key: <N8N_API_KEY>`
#### Respuesta de ejemplo (`200 OK`):
```json
[
{
"id": "1800fa1e-e969-469c-9913-6c973acba346",
"headline": "Aprende a tu ritmo",
"primary_text": "Clases de 25 minutos adaptadas a tu agenda laboral con nativos.",
"cta": "Empezar gratis",
"asset_url": "https://storage.4ventures.es/creatives/bps_v1.jpg",
"status": "aprobada",
"format": "estatico"
}
]
```
---
### 2.6 Notificar Publicación de Anuncio en Meta
Tras crear el anuncio en Meta Ads, n8n actualiza la creatividad con su identificador oficial y la pasa a `publicada`.
- **Método:** `PATCH`
- **Ruta:** `/api/integrations/creatives/:id`
- **Headers:** `X-Api-Key: <N8N_API_KEY>`, `Content-Type: application/json`
#### Payload de ejemplo (`Request Body`):
```json
{
"client_id": "8bec977a-6032-4759-aa7f-b5042081f450",
"meta_ad_id": "1202059998123456",
"status": "publicada"
}
```
#### Respuesta de ejemplo (`200 OK`):
```json
{
"id": "1800fa1e-e969-469c-9913-6c973acba346",
"meta_ad_id": "1202059998123456",
"status": "publicada",
"updated_at": "2026-10-12T11:47:10.455Z"
}
```
---
## 3. Endpoints para Claude (Generación de Relevo Creativo)
### 3.1 Obtener Contexto de la Creatividad Fatigada
Devuelve a Claude todos los datos necesarios para generar el relevo: el anuncio fatigado, sus métricas, las 3 mejores creatividades históricas del cliente y las reglas de fatiga.
- **Método:** `GET`
- **Ruta:** `/api/integrations/creatives/:id/context?client_id=8bec977a-6032-4759-aa7f-b5042081f450`
- **Headers:** `X-Api-Key: <CLAUDE_API_KEY>`
---
### 3.2 Crear Variantes Generadas
Inserta las variantes generadas por Claude. Nacen en estado `borrador`, con visibilidad `agency` y `created_by = 'claude'`.
- **Método:** `POST`
- **Ruta:** `/api/integrations/creatives`
- **Headers:** `X-Api-Key: <CLAUDE_API_KEY>`, `Content-Type: application/json`
#### Payload de ejemplo:
```json
{
"client_id": "8bec977a-6032-4759-aa7f-b5042081f450",
"generation_request_id": "377cf30e-81e5-4d80-8f4f-f30a8dd2d239",
"creatives": [
{
"format": "estatico",
"angle": "objecion_horarios",
"hook": "¿Sin tiempo para aprender inglés?",
"primary_text": "Clases de 25 minutos adaptadas a tu agenda.",
"headline": "Aprende a tu ritmo",
"cta": "Empezar gratis",
"asset_url": "https://storage.4ventures.es/creatives/bps_v1.jpg"
}
]
}
```
---
### 3.3 Completar Petición de Generación
Actualiza el estado de la tarea en la cola de Claude (`completada` o `fallida`), anotando los IDs generados y el coste estimado en tokens/créditos.
- **Método:** `PATCH`
- **Ruta:** `/api/integrations/generation-requests/:id`
- **Headers:** `X-Api-Key: <CLAUDE_API_KEY>`, `Content-Type: application/json`
#### Payload de ejemplo:
```json
{
"client_id": "8bec977a-6032-4759-aa7f-b5042081f450",
"status": "completada",
"result": {
"creatives": ["1800fa1e-e969-469c-9913-6c973acba346"],
"notes": "Variante enfocada en flexibilidad horaria",
"skills_used": ["cliente-blue-parrot", "formato-estatico-4-5"]
},
"cost_estimate": { "tokens": 8500, "credits": 1 }
}
```
---
## 4. Webhooks Salientes hacia n8n
La aplicación emite llamadas HTTP POST hacia la URL configurada en `N8N_WEBHOOK_BASE` (`https://n8n-agencia.4ventures.es/webhook/creative-...`).
Cada webhook se envía con la cabecera:
```http
X-Signature: <hex_digest_hmac_sha256>
```
### Eventos emitidos:
1. `creative.approved`: Un usuario cliente o de agencia aprueba la creatividad.
2. `creative.rejected`: Un usuario rechaza la creatividad (requiere comentario obligatorio).
3. `creative.sent_to_client`: La agencia envía la creatividad al cliente para su revisión.
---
## 5. Verificación de Firma HMAC en n8n (Nodo Code)
En el flujo de n8n que recibe el webhook (`Webhook Node`), agrega un nodo **Code** en modo `Run Once for Each Item` con el siguiente código JavaScript para verificar la autenticidad antes de procesar la petición:
```javascript
const crypto = require('crypto');
// 1. Obtener la firma enviada en la cabecera
const signature = $input.item.headers['x-signature'];
if (!signature) {
throw new Error('Cabecera X-Signature no proporcionada en el Webhook');
}
// 2. Secreto compartido (obtenido de las credenciales de n8n o variables de entorno)
const sharedSecret = $env['N8N_WEBHOOK_SECRET'] || 'whsec_4v_7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d';
// 3. Obtener el cuerpo recibido (raw string exacto o serializado)
const bodyPayload = typeof $input.item.body === 'string'
? $input.item.body
: JSON.stringify($input.item.body);
// 4. Calcular el HMAC esperado
const expectedSignature = crypto
.createHmac('sha256', sharedSecret)
.update(bodyPayload)
.digest('hex');
// 5. Comparación segura
const isValid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
if (!isValid) {
throw new Error('Firma HMAC no válida: webhook rechazado');
}
return {
json: {
verified: true,
event: $input.item.body.event,
data: $input.item.body.data
}
};
```