{"openapi":"3.0.0","paths":{"/v1/blogs/{id}":{"get":{"operationId":"BlogsController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicBlogDto"}}}},"401":{"description":"Clave ausente, no válida o revocada."},"404":{"description":"El blog no existe o es de otra organización."},"429":{"description":"Límite del plan superado (cabecera Retry-After)."}},"security":[{"bearer":[]}],"summary":"Obtiene un blog de tu organización","tags":["Blogs","Blogs"],"x-blogenia-api-key-access":true,"x-blogenia-scope":"content:read"}},"/v1/blogs/{blogId}/generations":{"get":{"description":"Permiso `account:read`. Estado, pasos (`key` y `status`), créditos y código de error; nunca prompts ni salidas del modelo. Las generaciones se conservan 30 días.","operationId":"GenerationsController_listActive","parameters":[{"name":"blogId","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":50,"default":20,"type":"number"}},{"name":"desde","required":false,"in":"query","description":"Desde este día (incluido), por fecha de creación.","schema":{"example":"2026-11-04","type":"string"}},{"name":"hasta","required":false,"in":"query","description":"Hasta este día (incluido), por fecha de creación.","schema":{"example":"2026-11-04","type":"string"}},{"name":"estado","required":false,"in":"query","schema":{"type":"string","enum":["queued","running","done","failed"]}},{"name":"status","required":false,"in":"query","description":"Solo en curso (queued o running).","schema":{"type":"string","enum":["active"]}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerationsPageDto"}}}},"404":{"description":"El blog no existe o es de otra organización."}},"security":[{"bearer":[]}],"summary":"Historial de generaciones de un blog","tags":["Generations","Generaciones"],"x-blogenia-api-key-access":true,"x-blogenia-scope":"account:read"}},"/v1/blogs/{blogId}/articles":{"get":{"description":"Solo artículos publicados, por fecha de publicación descendente. `status` solo admite `published`.","operationId":"ArticlesController_list","parameters":[{"name":"blogId","required":true,"in":"path","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","description":"Con clave de API solo admite `published`.","schema":{"type":"string","enum":["published"]}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"maximum":500,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":50,"default":20,"type":"number"}},{"name":"category","required":false,"in":"query","description":"Slug de la categoría.","schema":{"example":"reformas","type":"string"}},{"name":"publishedAfter","required":false,"in":"query","description":"ISO 8601: solo artículos publicados después.","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicArticleListDto"}}}},"400":{"description":"Parámetros no válidos."},"401":{"description":"Clave ausente, no válida o revocada."},"404":{"description":"El blog no existe o es de otra organización."},"429":{"description":"Límite del plan superado (cabecera Retry-After)."}},"security":[{"bearer":[]}],"summary":"Lista los artículos publicados de un blog","tags":["Articles","Artículos"],"x-blogenia-api-key-access":true,"x-blogenia-scope":"content:read"}},"/v1/articles/{id}":{"get":{"description":"Con `format=json` (por defecto) devuelve el artículo con `contentHtml` y `contentMarkdown`; con `format=html` solo el fragmento HTML (`text/html`) y con `format=markdown` el Markdown (`text/markdown`).","operationId":"ArticlesController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"format","required":false,"in":"query","description":"json (por defecto), html (text/html) o markdown (text/markdown).","schema":{"default":"json","type":"string","enum":["json","html","markdown"]}}],"responses":{"200":{"description":"JSON, HTML o Markdown según `format`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicArticleDetailDto"}},"text/html":{"schema":{"type":"string"}},"text/markdown":{"schema":{"type":"string"}}}},"400":{"description":"Formato no válido."},"401":{"description":"Clave ausente, no válida o revocada."},"404":{"description":"No existe, no está publicado o es de otra organización."},"429":{"description":"Límite del plan superado (cabecera Retry-After)."}},"security":[{"bearer":[]}],"summary":"Obtiene un artículo publicado","tags":["Articles","Artículos"],"x-blogenia-api-key-access":true,"x-blogenia-scope":"content:read"}},"/v1/blogs/{blogId}/autopilot":{"get":{"description":"Permiso `account:read`. Con clave devuelve la configuración, los próximos huecos y los últimos resultados; el panel recibe la vista completa.","operationId":"AutopilotController_get","parameters":[{"name":"blogId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutopilotKeyDto"}}}},"404":{"description":"El blog no existe o es de otra organización."}},"security":[{"bearer":[]}],"summary":"Configuración y huecos del piloto automático","tags":["Autopilot","Piloto automático"],"x-blogenia-api-key-access":true,"x-blogenia-scope":"account:read"}},"/v1/account":{"get":{"description":"Permiso `account:read`. Organización, créditos, uso del mes, límites y blogs.","operationId":"AccountApiController_account","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDto"}}}},"401":{"description":"Clave ausente, no válida o revocada."},"403":{"description":"La clave no tiene el permiso (`insufficient_scope`)."},"429":{"description":"Límite del plan superado (cabecera Retry-After)."}},"security":[{"bearer":[]}],"summary":"Datos de la cuenta","tags":["AccountApi","Cuenta"],"x-blogenia-api-key-access":true,"x-blogenia-scope":"account:read"}},"/v1/account/credits/ledger":{"get":{"description":"Permiso `account:read`. Más reciente primero; `amount` lleva signo.","operationId":"AccountApiController_ledger","parameters":[{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":50,"default":20,"type":"number"}},{"name":"from","required":false,"in":"query","description":"Fecha YYYY-MM-DD (Europe/Madrid).","schema":{"example":"2026-11-04","type":"string"}},{"name":"to","required":false,"in":"query","description":"Fecha YYYY-MM-DD (Europe/Madrid).","schema":{"example":"2026-11-04","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LedgerPageDto"}}}},"400":{"description":"Fechas o paginación no válidas."},"401":{"description":"Clave ausente, no válida o revocada."},"403":{"description":"La clave no tiene el permiso (`insufficient_scope`)."},"429":{"description":"Límite del plan superado (cabecera Retry-After)."}},"security":[{"bearer":[]}],"summary":"Movimientos de créditos","tags":["AccountApi","Cuenta"],"x-blogenia-api-key-access":true,"x-blogenia-scope":"account:read"}},"/v1/account/stats":{"get":{"description":"Permiso `stats:read`. Los datos vienen del rollup (hasta 10 minutos de retraso, ver `updatedAt`); el día de hoy es parcial (`partial: true`). Fechas en Europe/Madrid; por defecto los últimos 30 días, máximo 400. `uniquesApprox` es aproximado.","operationId":"AccountApiController_accountStats","parameters":[{"name":"from","required":false,"in":"query","description":"Fecha YYYY-MM-DD (Europe/Madrid).","schema":{"example":"2026-11-04","type":"string"}},{"name":"to","required":false,"in":"query","description":"Fecha YYYY-MM-DD (Europe/Madrid).","schema":{"example":"2026-11-04","type":"string"}},{"name":"interval","required":false,"in":"query","schema":{"default":"day","type":"string","enum":["day","week","month"]}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountStatsDto"}}}},"400":{"description":"Fechas no válidas (`invalid_date`, `invalid_range`, `range_in_future`, `range_too_long`)."},"401":{"description":"Clave ausente, no válida o revocada."},"403":{"description":"La clave no tiene el permiso (`insufficient_scope`)."},"429":{"description":"Límite del plan superado (cabecera Retry-After)."}},"security":[{"bearer":[]}],"summary":"Visitas de toda la cuenta","tags":["AccountApi","Analítica"],"x-blogenia-api-key-access":true,"x-blogenia-scope":"stats:read"}},"/v1/blogs/{blogId}/stats":{"get":{"description":"Permiso `stats:read`. Los datos vienen del rollup (hasta 10 minutos de retraso, ver `updatedAt`); el día de hoy es parcial (`partial: true`). Fechas en Europe/Madrid; por defecto los últimos 30 días, máximo 400. `uniquesApprox` es aproximado.","operationId":"AccountApiController_blogStats","parameters":[{"name":"blogId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":false,"in":"query","description":"Fecha YYYY-MM-DD (Europe/Madrid).","schema":{"example":"2026-11-04","type":"string"}},{"name":"to","required":false,"in":"query","description":"Fecha YYYY-MM-DD (Europe/Madrid).","schema":{"example":"2026-11-04","type":"string"}},{"name":"interval","required":false,"in":"query","schema":{"default":"day","type":"string","enum":["day","week","month"]}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogStatsDto"}}}},"400":{"description":"Fechas no válidas."},"401":{"description":"Clave ausente, no válida o revocada."},"403":{"description":"La clave no tiene el permiso (`insufficient_scope`)."},"404":{"description":"El blog no existe o es de otra organización."},"429":{"description":"Límite del plan superado (cabecera Retry-After)."}},"security":[{"bearer":[]}],"summary":"Visitas de un blog","tags":["AccountApi","Analítica"],"x-blogenia-api-key-access":true,"x-blogenia-scope":"stats:read"}},"/v1/blogs/{blogId}/stats/articles":{"get":{"description":"Permiso `stats:read`. Los datos vienen del rollup (hasta 10 minutos de retraso, ver `updatedAt`); el día de hoy es parcial (`partial: true`). Fechas en Europe/Madrid; por defecto los últimos 30 días, máximo 400. `uniquesApprox` es aproximado.","operationId":"AccountApiController_ranking","parameters":[{"name":"blogId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":false,"in":"query","description":"Fecha YYYY-MM-DD (Europe/Madrid).","schema":{"example":"2026-11-04","type":"string"}},{"name":"to","required":false,"in":"query","description":"Fecha YYYY-MM-DD (Europe/Madrid).","schema":{"example":"2026-11-04","type":"string"}},{"name":"sort","required":false,"in":"query","schema":{"default":"-views","type":"string","enum":["views","-views"]}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArticleRankingDto"}}}},"400":{"description":"Fechas o parámetros no válidos."},"401":{"description":"Clave ausente, no válida o revocada."},"403":{"description":"La clave no tiene el permiso (`insufficient_scope`)."},"404":{"description":"El blog no existe o es de otra organización."},"429":{"description":"Límite del plan superado (cabecera Retry-After)."}},"security":[{"bearer":[]}],"summary":"Ranking de artículos por visitas","tags":["AccountApi","Analítica"],"x-blogenia-api-key-access":true,"x-blogenia-scope":"stats:read"}},"/v1/articles/{id}/stats":{"get":{"description":"Permiso `stats:read`. Los datos vienen del rollup (hasta 10 minutos de retraso, ver `updatedAt`); el día de hoy es parcial (`partial: true`). Fechas en Europe/Madrid; por defecto los últimos 30 días, máximo 400. `uniquesApprox` es aproximado.","operationId":"AccountApiController_articleStats","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":false,"in":"query","description":"Fecha YYYY-MM-DD (Europe/Madrid).","schema":{"example":"2026-11-04","type":"string"}},{"name":"to","required":false,"in":"query","description":"Fecha YYYY-MM-DD (Europe/Madrid).","schema":{"example":"2026-11-04","type":"string"}},{"name":"interval","required":false,"in":"query","schema":{"default":"day","type":"string","enum":["day","week","month"]}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArticleStatsDto"}}}},"400":{"description":"Fechas no válidas."},"401":{"description":"Clave ausente, no válida o revocada."},"403":{"description":"La clave no tiene el permiso (`insufficient_scope`)."},"404":{"description":"El artículo no existe o es de otra organización."},"429":{"description":"Límite del plan superado (cabecera Retry-After)."}},"security":[{"bearer":[]}],"summary":"Visitas de un artículo","tags":["AccountApi","Analítica"],"x-blogenia-api-key-access":true,"x-blogenia-scope":"stats:read"}},"/v1/blogs/{blogId}/articles/all":{"get":{"description":"Permiso `account:read`. Sin el contenido: el texto completo de los publicados está en `GET /articles/:id` (`content:read`).","operationId":"AccountApiController_articlesAll","parameters":[{"name":"blogId","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":50,"default":20,"type":"number"}},{"name":"desde","required":false,"in":"query","description":"Desde este día (incluido), por fecha de creación.","schema":{"example":"2026-11-04","type":"string"}},{"name":"hasta","required":false,"in":"query","description":"Hasta este día (incluido), por fecha de creación.","schema":{"example":"2026-11-04","type":"string"}},{"name":"estado","required":false,"in":"query","schema":{"type":"string","enum":["draft","pending_approval","scheduled","published","rejected","unpublished"]}},{"name":"q","required":false,"in":"query","description":"Texto del título.","schema":{"maxLength":80,"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArticlesAllPageDto"}}}},"400":{"description":"Parámetros no válidos."},"401":{"description":"Clave ausente, no válida o revocada."},"403":{"description":"La clave no tiene el permiso (`insufficient_scope`)."},"404":{"description":"El blog no existe o es de otra organización."},"429":{"description":"Límite del plan superado (cabecera Retry-After)."}},"security":[{"bearer":[]}],"summary":"Artículos de un blog en cualquier estado","tags":["AccountApi","Artículos (todos los estados)"],"x-blogenia-api-key-access":true,"x-blogenia-scope":"account:read"}},"/v1/blogs/{blogId}/articles/summary":{"get":{"description":"Permiso `account:read`. Recuento por estado, publicados este mes y el anterior, próximos programados y pendientes de aprobar.","operationId":"AccountApiController_articlesSummary","parameters":[{"name":"blogId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArticlesSummaryDto"}}}},"401":{"description":"Clave ausente, no válida o revocada."},"403":{"description":"La clave no tiene el permiso (`insufficient_scope`)."},"404":{"description":"El blog no existe o es de otra organización."},"429":{"description":"Límite del plan superado (cabecera Retry-After)."}},"security":[{"bearer":[]}],"summary":"Resumen de artículos de un blog","tags":["AccountApi","Artículos (todos los estados)"],"x-blogenia-api-key-access":true,"x-blogenia-scope":"account:read"}},"/v1/blogs/{blogId}/publications":{"get":{"description":"Permiso `account:read`. Estado de cada artículo enviado a WordPress o a tu API.","operationId":"AccountApiController_publications","parameters":[{"name":"blogId","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":50,"default":20,"type":"number"}},{"name":"estado","required":false,"in":"query","schema":{"type":"string","enum":["pending","processing","succeeded","failed","needs_attention"]}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicationsPageDto"}}}},"401":{"description":"Clave ausente, no válida o revocada."},"403":{"description":"La clave no tiene el permiso (`insufficient_scope`)."},"404":{"description":"El blog no existe o es de otra organización."},"429":{"description":"Límite del plan superado (cabecera Retry-After)."}},"security":[{"bearer":[]}],"summary":"Publicación externa de un blog","tags":["AccountApi","Entregas"],"x-blogenia-api-key-access":true,"x-blogenia-scope":"account:read"}},"/v1/webhooks/deliveries":{"get":{"description":"Permiso `account:read`. Sin cuerpo ni secreto. Más reciente primero.","operationId":"AccountApiController_webhookDeliveries","parameters":[{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":50,"default":20,"type":"number"}},{"name":"estado","required":false,"in":"query","schema":{"type":"string","enum":["pending","delivered","failed"]}},{"name":"desde","required":false,"in":"query","description":"Desde este día (incluido).","schema":{"example":"2026-11-04","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveriesPageDto"}}}},"400":{"description":"Parámetros no válidos."},"401":{"description":"Clave ausente, no válida o revocada."},"403":{"description":"La clave no tiene el permiso (`insufficient_scope`)."},"429":{"description":"Límite del plan superado (cabecera Retry-After)."}},"security":[{"bearer":[]}],"summary":"Entregas de webhooks","tags":["AccountApi","Entregas"],"x-blogenia-api-key-access":true,"x-blogenia-scope":"account:read"}}},"info":{"title":"Blogenia · API pública","description":"API pública de lectura de Blogenia: tus blogs y artículos publicados, en JSON, HTML o Markdown.\n\n## Autenticación\n\nCrea una clave en el dashboard (pantalla **API**, planes Starter y Pro) y envíala como\n`Authorization: Bearer pk_live_…`. La clave solo se muestra una vez al crearla. Solo funciona en las\nrutas de lectura de este documento y solo ve datos de tu organización.\n\nCada clave lleva los permisos que eliges al crearla y no cambian después: `content:read` (contenido\npublicado, el de las claves anteriores), `stats:read` (analítica de visitas) y `account:read` (cuenta,\ncréditos, artículos en cualquier estado, generaciones, piloto y entregas). Sin el permiso de la ruta,\n`403` con `code: insufficient_scope`.\n\n**No pongas la clave en código que se ejecute en el navegador.** Las rutas de `content:read` aceptan\npeticiones desde cualquier origen (sin cookies), pero cualquiera podría leer la clave desde tu web. Las de\n`stats:read` y `account:read` no admiten CORS: llámalas desde tu servidor.\n\n## Límite de peticiones\n\n60 por minuto en Starter y 300 en Pro, por clave. Cada respuesta con clave lleva `X-RateLimit-Limit`,\n`X-RateLimit-Remaining` y `X-RateLimit-Reset` (segundos hasta que se reinicia la ventana). Al\nsuperarlo, `429` con `Retry-After`. Las respuestas no se cachean (`Cache-Control: private, no-store`).\n\n## Webhook `article.published`\n\nCuando se publica un artículo (a mano, programado o por el piloto automático) enviamos un `POST` JSON\na cada webhook activo de tu organización suscrito al evento. El cuerpo es el esquema\n`WebhookPayloadDto` (más abajo). Cabeceras:\n\n| Cabecera | Contenido |\n|---|---|\n| `Blogenia-Event` | `article.published` (o `ping` en las pruebas) |\n| `Blogenia-Delivery` | Id de la entrega (igual que `id` del cuerpo) |\n| `Blogenia-Timestamp` | Segundos Unix en que se firmó |\n| `Blogenia-Signature` | `v1=<hex>`, HMAC-SHA256 con tu secreto (`whsec_…`) |\n| `User-Agent` | `Blogenia-Webhooks/1.0` |\n\n**Verificar la firma:** calcula `HMAC-SHA256(secreto, \"<timestamp>.<cuerpo en bruto>\")` en hexadecimal,\ncompáralo en tiempo constante con el valor tras `v1=` y **rechaza** las peticiones con un timestamp de\nmás de 5 minutos de antigüedad. Usa el cuerpo en bruto, sin volver a serializar el JSON.\n\n```js\nconst crypto = require('node:crypto');\nfunction verify(secret, rawBody, headers) {\n  const ts = headers['blogenia-timestamp'];\n  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;\n  const expected = 'v1=' + crypto.createHmac('sha256', secret).update(ts + '.' + rawBody).digest('hex');\n  const received = headers['blogenia-signature'] || '';\n  return received.length === expected.length &&\n    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));\n}\n```\n\n**Respuesta y reintentos:** responde con cualquier `2xx` en menos de 10 s. Cualquier otra respuesta o error\nde red cuenta como fallo y se reintenta hasta 5 intentos en total (el primero inmediato y luego a 1 min,\n5 min, 30 min y 2 h). Si respondes `410 Gone`, el webhook se desactiva y no se reintenta. No seguimos\nredirecciones. Usa el `id` de la entrega para ignorar duplicados.","version":"1.0","contact":{}},"tags":[],"servers":[],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"pk_live_…","type":"http","description":"Clave de API creada en el dashboard: `pk_live_` y 32 caracteres hexadecimales."}},"schemas":{"PublicCategoryRefDto":{"type":"object","properties":{"slug":{"type":"string","example":"reformas"},"name":{"type":"string","example":"Reformas"}},"required":["slug","name"]},"PublicAuthorDto":{"type":"object","properties":{"name":{"type":"string","example":"Reformas Ejemplo"},"bio":{"type":"string","example":"Reformas integrales en Madrid desde 2008."}},"required":["name","bio"]},"PublicBlogDto":{"type":"object","properties":{"id":{"type":"string","example":"66f1c0a2b3c4d5e6f7a8b9c0"},"name":{"type":"string","example":"Reformas Ejemplo"},"slug":{"type":"string","example":"reformas-ejemplo"},"url":{"type":"string","example":"https://reformas-ejemplo.blogenia-blogs.com"},"description":{"type":"string"},"language":{"type":"string","example":"es"},"category":{"$ref":"#/components/schemas/PublicCategoryRefDto"},"author":{"$ref":"#/components/schemas/PublicAuthorDto"},"articleCount":{"type":"number","description":"Artículos publicados.","example":12}},"required":["id","name","slug","url","description","language","category","author","articleCount"]},"GenerationStepKeyDto":{"type":"object","properties":{"key":{"type":"string"},"status":{"type":"string","enum":["pending","running","done","failed","skipped"]}},"required":["key","status"]},"GenerationItemDto":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["queued","running","done","failed"]},"source":{"type":"string","enum":["topic","keyword","brief","autopilot"]},"keyword":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"finishedAt":{"type":"string","format":"date-time"},"articleId":{"type":"string"},"error":{"type":"object","example":{"code":"ai_unavailable"}},"credits":{"type":"object","example":{"estimated":8,"charged":7}},"steps":{"type":"array","items":{"$ref":"#/components/schemas/GenerationStepKeyDto"}}},"required":["id","status","source","createdAt","steps"]},"GenerationsPageDto":{"type":"object","properties":{"page":{"type":"number"},"limit":{"type":"number"},"total":{"type":"number"},"data":{"type":"array","items":{"$ref":"#/components/schemas/GenerationItemDto"}}},"required":["page","limit","total","data"]},"PublicCoverImageDto":{"type":"object","properties":{"url":{"type":"string"},"alt":{"type":"string"}},"required":["url","alt"]},"PublicFaqDto":{"type":"object","properties":{"q":{"type":"string","example":"¿Cuánto cuesta reformar una cocina?"},"a":{"type":"string"}},"required":["q","a"]},"PublicArticleItemDto":{"type":"object","properties":{"id":{"type":"string","example":"66f1c0a2b3c4d5e6f7a8b9c1"},"title":{"type":"string"},"slug":{"type":"string","example":"cuanto-cuesta-reformar-una-cocina"},"url":{"type":"string","description":"URL pública del artículo en el blog."},"excerpt":{"type":"string"},"publishedAt":{"type":"string","description":"ISO 8601 con zona.","example":"2026-10-01T09:00:00.000Z"},"updatedAt":{"type":"string","description":"ISO 8601 con zona.","example":"2026-10-02T10:30:00.000Z"},"category":{"$ref":"#/components/schemas/PublicCategoryRefDto"},"coverImage":{"nullable":true,"type":"object","allOf":[{"$ref":"#/components/schemas/PublicCoverImageDto"}]},"readingMinutes":{"type":"number","example":5},"faqs":{"description":"Preguntas frecuentes del artículo (vacío si no tiene).","type":"array","items":{"$ref":"#/components/schemas/PublicFaqDto"}}},"required":["id","title","slug","url","excerpt","publishedAt","updatedAt","category","coverImage","readingMinutes","faqs"]},"PublicArticleListDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicArticleItemDto"}},"page":{"type":"number","example":1},"limit":{"type":"number","example":20},"total":{"type":"number","example":12}},"required":["data","page","limit","total"]},"PublicSeoDto":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"canonical":{"type":"string"}},"required":["title","description","canonical"]},"PublicArticleDetailDto":{"type":"object","properties":{"id":{"type":"string","example":"66f1c0a2b3c4d5e6f7a8b9c1"},"title":{"type":"string"},"slug":{"type":"string","example":"cuanto-cuesta-reformar-una-cocina"},"url":{"type":"string","description":"URL pública del artículo en el blog."},"excerpt":{"type":"string"},"publishedAt":{"type":"string","description":"ISO 8601 con zona.","example":"2026-10-01T09:00:00.000Z"},"updatedAt":{"type":"string","description":"ISO 8601 con zona.","example":"2026-10-02T10:30:00.000Z"},"category":{"$ref":"#/components/schemas/PublicCategoryRefDto"},"coverImage":{"nullable":true,"type":"object","allOf":[{"$ref":"#/components/schemas/PublicCoverImageDto"}]},"readingMinutes":{"type":"number","example":5},"faqs":{"description":"Preguntas frecuentes del artículo (vacío si no tiene).","type":"array","items":{"$ref":"#/components/schemas/PublicFaqDto"}},"contentHtml":{"type":"string","description":"Fragmento HTML saneado, listo para incrustar."},"contentMarkdown":{"type":"string","description":"El mismo contenido en Markdown."},"seo":{"$ref":"#/components/schemas/PublicSeoDto"},"sponsored":{"type":"boolean","description":"Artículo patrocinado: los enlaces externos llevan rel=sponsored."}},"required":["id","title","slug","url","excerpt","publishedAt","updatedAt","category","coverImage","readingMinutes","faqs","contentHtml","contentMarkdown","seo","sponsored"]},"AutopilotNextSlotDto":{"type":"object","properties":{"slotAt":{"type":"string","format":"date-time"},"status":{"type":"string"},"topicTitle":{"type":"string"}},"required":["slotAt","status"]},"AutopilotLastSlotDto":{"type":"object","properties":{"slotAt":{"type":"string","format":"date-time"},"result":{"type":"string","description":"Resultado del hueco (generating, scheduled, published, skipped...)."},"articleId":{"type":"string"},"reason":{"type":"string"}},"required":["slotAt","result"]},"AutopilotKeyDto":{"type":"object","properties":{"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["review","auto"]},"days":{"description":"Días de la semana (0 = domingo).","type":"array","items":{"type":"number"}},"time":{"type":"string","example":"09:00","description":"Hora de publicación en Europe/Madrid."},"scheduleType":{"type":"string","enum":["weekly","interval"]},"nextSlots":{"type":"array","items":{"$ref":"#/components/schemas/AutopilotNextSlotDto"}},"lastSlots":{"type":"array","items":{"$ref":"#/components/schemas/AutopilotLastSlotDto"}},"credits":{"type":"object","example":{"perArticle":8,"available":120}}},"required":["enabled","mode","days","time","scheduleType","nextSlots","lastSlots","credits"]},"AccountOrganizationDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"plan":{"type":"string","enum":["free","starter","pro"]},"createdAt":{"type":"string","format":"date-time"}},"required":["id","name","plan","createdAt"]},"CreditBucketDto":{"type":"object","properties":{"period":{"type":"string","example":"2026-11","description":"Mes del lote (YYYY-MM, UTC)."},"remaining":{"type":"number","description":"Créditos que quedan en el lote."},"expiresAt":{"type":"string","format":"date-time"}},"required":["period","remaining","expiresAt"]},"AccountCreditsDto":{"type":"object","properties":{"balance":{"type":"number","description":"Créditos disponibles (lotes no caducados menos reservas)."},"buckets":{"type":"array","items":{"$ref":"#/components/schemas/CreditBucketDto"}}},"required":["balance","buckets"]},"AccountUsageDto":{"type":"object","properties":{"period":{"type":"string","example":"2026-11","description":"Mes natural en Europe/Madrid."},"articlesPublished":{"type":"number"},"articlesGenerated":{"type":"number"},"generationsFailed":{"type":"number"}},"required":["period","articlesPublished","articlesGenerated","generationsFailed"]},"AccountLimitsDto":{"type":"object","properties":{"blogs":{"type":"number"},"apiRatePerMin":{"type":"number"},"autopilotPerWeek":{"type":"number"}},"required":["blogs","apiRatePerMin","autopilotPerWeek"]},"AccountBlogDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"publicUrl":{"type":"string"},"status":{"type":"string","enum":["active","suspended"]},"createdAt":{"type":"string","format":"date-time"}},"required":["id","name","slug","publicUrl","status","createdAt"]},"AccountDto":{"type":"object","properties":{"organization":{"$ref":"#/components/schemas/AccountOrganizationDto"},"credits":{"$ref":"#/components/schemas/AccountCreditsDto"},"usage":{"$ref":"#/components/schemas/AccountUsageDto"},"limits":{"$ref":"#/components/schemas/AccountLimitsDto"},"blogs":{"type":"array","items":{"$ref":"#/components/schemas/AccountBlogDto"}}},"required":["organization","credits","usage","limits","blogs"]},"LedgerEntryDto":{"type":"object","properties":{"at":{"type":"string","format":"date-time"},"type":{"type":"string","enum":["grant","reserve","settle","release","expire","refund","admin"],"description":"`reserve` y `release` son movimientos internos de reserva: para el saldo cuenta `settle`."},"amount":{"type":"number","description":"Créditos con signo (negativo = gasto)."},"ref":{"type":"string"},"articleId":{"type":"string"},"generationId":{"type":"string"}},"required":["at","type","amount"]},"LedgerPageDto":{"type":"object","properties":{"page":{"type":"number"},"limit":{"type":"number"},"total":{"type":"number"},"data":{"type":"array","items":{"$ref":"#/components/schemas/LedgerEntryDto"}}},"required":["page","limit","total","data"]},"StatsTotalsDto":{"type":"object","properties":{"views":{"type":"number"},"uniquesApproxSum":{"type":"number","description":"Suma de los únicos diarios (aproximados): no es el número de personas distintas."},"articlesPublished":{"type":"number"}},"required":["views","uniquesApproxSum","articlesPublished"]},"StatsPointDto":{"type":"object","properties":{"date":{"type":"string","example":"2026-11-02","description":"Primer día del grupo (lunes o día 1)."},"views":{"type":"number"},"uniquesApprox":{"type":"number","description":"Aproximado (HMAC diario con sal, sin cookies)."},"partial":{"type":"boolean","description":"true si el grupo incluye hoy y aún no ha terminado."}},"required":["date","views","uniquesApprox"]},"AccountStatsBlogDto":{"type":"object","properties":{"blogId":{"type":"string"},"name":{"type":"string"},"views":{"type":"number"}},"required":["blogId","name","views"]},"AccountStatsDto":{"type":"object","properties":{"from":{"type":"string"},"to":{"type":"string"},"interval":{"type":"string","enum":["day","week","month"]},"totals":{"$ref":"#/components/schemas/StatsTotalsDto"},"series":{"type":"array","items":{"$ref":"#/components/schemas/StatsPointDto"}},"blogs":{"type":"array","items":{"$ref":"#/components/schemas/AccountStatsBlogDto"}},"updatedAt":{"type":"string","format":"date-time","nullable":true}},"required":["from","to","interval","totals","series","blogs","updatedAt"]},"BlogStatsDto":{"type":"object","properties":{"from":{"type":"string","example":"2026-10-06"},"to":{"type":"string","example":"2026-11-04"},"interval":{"type":"string","enum":["day","week","month"]},"totals":{"$ref":"#/components/schemas/StatsTotalsDto"},"series":{"type":"array","items":{"$ref":"#/components/schemas/StatsPointDto"}},"updatedAt":{"type":"string","format":"date-time","nullable":true,"description":"Hora del último rollup (hasta 10 min de retraso)."}},"required":["from","to","interval","totals","series","updatedAt"]},"RankedArticleDto":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"publishedAt":{"type":"string","format":"date-time","nullable":true},"views":{"type":"number"}},"required":["articleId","title","slug","url","publishedAt","views"]},"ArticleRankingDto":{"type":"object","properties":{"from":{"type":"string"},"to":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/RankedArticleDto"}},"updatedAt":{"type":"string","format":"date-time","nullable":true}},"required":["from","to","data","updatedAt"]},"ArticleStatsPointDto":{"type":"object","properties":{"date":{"type":"string"},"views":{"type":"number"},"partial":{"type":"boolean"}},"required":["date","views"]},"ArticleStatsDto":{"type":"object","properties":{"from":{"type":"string"},"to":{"type":"string"},"interval":{"type":"string","enum":["day","week","month"]},"totals":{"type":"object","example":{"views":120}},"series":{"type":"array","items":{"$ref":"#/components/schemas/ArticleStatsPointDto"}},"updatedAt":{"type":"string","format":"date-time","nullable":true}},"required":["from","to","interval","totals","series","updatedAt"]},"ArticleAllItemDto":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string","enum":["draft","pending_approval","scheduled","published","rejected","unpublished"]},"source":{"type":"string","enum":["manual","generated","autopilot"]},"createdAt":{"type":"string","format":"date-time"},"scheduledAt":{"type":"string","format":"date-time"},"publishedAt":{"type":"string","format":"date-time"},"wordCount":{"type":"number"},"requiresHumanReview":{"type":"boolean"},"url":{"type":"string","description":"Solo artículos publicados."}},"required":["id","title","slug","status","source","createdAt","wordCount","requiresHumanReview"]},"ArticlesAllPageDto":{"type":"object","properties":{"page":{"type":"number"},"limit":{"type":"number"},"total":{"type":"number"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ArticleAllItemDto"}}},"required":["page","limit","total","data"]},"ArticleBriefDto":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"scheduledAt":{"type":"string","format":"date-time"}},"required":["id","title"]},"ArticlesSummaryDto":{"type":"object","properties":{"counts":{"type":"object","example":{"draft":2,"pending_approval":1,"scheduled":3,"published":20,"unpublished":0,"rejected":0}},"period":{"type":"string","example":"2026-11","description":"Mes natural en Europe/Madrid."},"publishedThisMonth":{"type":"number"},"publishedPreviousMonth":{"type":"number"},"upcomingScheduled":{"description":"Hasta 10, el más próximo primero.","type":"array","items":{"$ref":"#/components/schemas/ArticleBriefDto"}},"pendingApproval":{"description":"Hasta 10 pendientes de aprobar.","type":"array","items":{"$ref":"#/components/schemas/ArticleBriefDto"}}},"required":["counts","period","publishedThisMonth","publishedPreviousMonth","upcomingScheduled","pendingApproval"]},"PublicationItemDto":{"type":"object","properties":{"articleId":{"type":"string"},"provider":{"type":"string","enum":["wordpress","custom"]},"status":{"type":"string","enum":["pending","processing","succeeded","failed","needs_attention"]},"remoteUrl":{"type":"string"},"lastAttemptAt":{"type":"string","format":"date-time"},"attempts":{"type":"number"},"error":{"type":"string"}},"required":["articleId","provider","status","lastAttemptAt","attempts"]},"PublicationsPageDto":{"type":"object","properties":{"page":{"type":"number"},"limit":{"type":"number"},"total":{"type":"number"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicationItemDto"}}},"required":["page","limit","total","data"]},"WebhookDeliveryItemDto":{"type":"object","properties":{"id":{"type":"string"},"webhookId":{"type":"string"},"event":{"type":"string"},"status":{"type":"string","enum":["pending","delivered","failed"]},"attempts":{"type":"number"},"lastStatusCode":{"type":"number"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","webhookId","event","status","attempts","createdAt"]},"WebhookDeliveriesPageDto":{"type":"object","properties":{"page":{"type":"number"},"limit":{"type":"number"},"total":{"type":"number"},"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDeliveryItemDto"}}},"required":["page","limit","total","data"]},"WebhookArticleDataDto":{"type":"object","properties":{"article":{"$ref":"#/components/schemas/PublicArticleItemDto"},"blogId":{"type":"string","example":"66f1c0a2b3c4d5e6f7a8b9c0"}},"required":["article","blogId"]},"WebhookPayloadDto":{"type":"object","properties":{"id":{"type":"string","description":"Id de la entrega (el mismo de la cabecera Blogenia-Delivery)."},"event":{"type":"string","enum":["article.published","ping"],"example":"article.published"},"createdAt":{"type":"string","description":"ISO 8601 con zona."},"data":{"description":"En `ping` es `{ webhookId }`.","allOf":[{"$ref":"#/components/schemas/WebhookArticleDataDto"}]}},"required":["id","event","createdAt","data"]}}}}