# Generaciones

> Historial de generaciones de artículos de un blog de Blogenia por API: estado, pasos, créditos y código de error, con el permiso account:read.

Versión web: https://blogelia.com/docs/generaciones

Una generación es el trabajo que escribe un artículo. Esta ruta lista el historial de un blog para ver qué se ha generado, qué está en marcha y qué ha fallado.

> **Aviso:** Exige una clave con el permiso `account:read`. Úsala solo desde tu servidor. Ver [Permisos](https://blogelia.com/docs/autenticacion#scopes).

## Historial de generaciones

**`GET /blogs/{blogId}/generations`**: Lista paginada de generaciones, de la más reciente a la más antigua.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
| --- | --- | --- | --- |
| `blogId` | string | Sí | Identificador del blog (en la ruta). |
| `estado` | string | No | `queued`, `running`, `done` o `failed`. |
| `status` | string | No | Solo admite `active`: las que están en curso (`queued` o `running`). Si lo envías, se ignora `estado`. |
| `desde` | string | No | Día `YYYY-MM-DD` (Europe/Madrid), incluido: solo elementos creados desde ese día. |
| `hasta` | string | No | Día `YYYY-MM-DD` (Europe/Madrid), incluido: solo elementos creados hasta ese día. No puede ser anterior a `desde`. |
| `page` | integer | No | Página, desde 1 (por defecto 1, máximo 10000). |
| `limit` | integer | No | Elementos por página, de 1 a 50 (por defecto 20). |

**Campos de la respuesta**

| Campo | Tipo | Descripción |
| --- | --- | --- |
| `data` | array | Elementos de la página. |
| `page` | integer | Página devuelta. |
| `limit` | integer | Tamaño de página aplicado. |
| `total` | integer | Total de elementos que cumplen el filtro, en todas las páginas. |
| `data[].id` | string | Identificador de la generación. |
| `data[].status` | string | `queued`, `running`, `done` o `failed`. |
| `data[].source` | string | Cómo se pidió: `topic`, `keyword`, `brief` o `autopilot`. |
| `data[].keyword` | string | Palabra clave, si se indicó. |
| `data[].createdAt` | string | Fecha de creación, ISO 8601 en UTC. |
| `data[].finishedAt` | string | Fecha de fin. Solo si ha terminado. |
| `data[].articleId` | string | Artículo resultante. Solo en las que acabaron bien (`done`). |
| `data[].error` | object | `{ code }`. Solo en las fallidas. `code` puede ser `ai_disabled`, `ai_unavailable`, `cost_cap`, `quota`, `duplicate_topic` o `internal`. |
| `data[].credits` | object | `{ estimated, charged? }`: créditos estimados y, cuando se ha cobrado, los cobrados. |
| `data[].steps` | array | Pasos: `[{ key, status }]`. `key` es `outline`, `sections`, `verify`, `links`, `faq`, `review`, `cover` o `save`; `status` es `pending`, `running`, `done`, `failed` o `skipped`. |

> **Nota:** Las generaciones se conservan 30 días. La respuesta nunca incluye prompts ni salidas del modelo.

**Petición**

*curl*

```bash
curl "https://api.blogelia.com/v1/blogs/66f1c0a2b3c4d5e6f7a8b9c0/generations?estado=done&limit=10" \
  -H "Authorization: Bearer $BLOGENIA_API_KEY"
```

*Node.js*

```js
const res = await fetch('https://api.blogelia.com/v1/blogs/66f1c0a2b3c4d5e6f7a8b9c0/generations?estado=done&limit=10', {
  headers: { Authorization: 'Bearer ' + process.env.BLOGENIA_API_KEY },
});
if (!res.ok) throw new Error('HTTP ' + res.status);
const body = await res.json();
console.log(body);
```

*Python*

```python
import os
import requests

res = requests.get(
    "https://api.blogelia.com/v1/blogs/66f1c0a2b3c4d5e6f7a8b9c0/generations",
    params={"estado": "done", "limit": 10},
    headers={"Authorization": "Bearer " + os.environ["BLOGENIA_API_KEY"]},
    timeout=10,
)
res.raise_for_status()
print(res.json())
```

**Respuesta**

```json
{
  "data": [
    {
      "id": "66f1c0a2b3c4d5e6f7a8b9e0",
      "status": "done",
      "source": "autopilot",
      "keyword": "reforma de cocina precio",
      "createdAt": "2026-11-03T08:58:00.000Z",
      "finishedAt": "2026-11-03T09:00:41.000Z",
      "articleId": "66f1c0a2b3c4d5e6f7a8b9c1",
      "credits": { "estimated": 8, "charged": 8 },
      "steps": [
        { "key": "outline", "status": "done" },
        { "key": "sections", "status": "done" },
        { "key": "verify", "status": "done" },
        { "key": "links", "status": "done" },
        { "key": "faq", "status": "skipped" },
        { "key": "review", "status": "done" },
        { "key": "cover", "status": "done" },
        { "key": "save", "status": "done" }
      ]
    }
  ],
  "page": 1,
  "limit": 10,
  "total": 1
}
```

**Respuestas**

| Código | Cuándo |
| --- | --- |
| `200` | Respuesta devuelta. |
| `400` | Parámetro no válido (estado desconocido, fechas con `invalid_date` o `invalid_range`) o parámetro desconocido. |
| `404` | El blog no existe o es de otra organización. |
| `401` | Clave ausente, no válida o revocada. |
| `403` | La clave no tiene el permiso `account:read` (`code: "insufficient_scope"`), tu plan es Free (`plan_required`) o la organización está suspendida (`ORGANIZATION_SUSPENDED`). |
| `429` | Límite del plan superado. Ver [Límites de uso](https://blogelia.com/docs/limites). |
