365 lines
10 KiB
Markdown
365 lines
10 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"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 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
|
|
}
|
|
};
|
|
```
|