# Entregas

> Estado de la publicación externa (WordPress o tu API) y de las entregas de webhooks de Blogenia por API, con el permiso account:read.

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

Dos rutas te dicen si Blogenia ha entregado bien tus artículos: la publicación externa en WordPress o en tu propia API, y los avisos de [webhooks](https://blogelia.com/docs/webhooks).

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

## Publicación externa de un blog

**`GET /blogs/{blogId}/publications`**: Estado de cada artículo enviado a WordPress o a tu API.

**Parámetros**

| Nombre | Tipo | Obligatorio | Descripción |
| --- | --- | --- | --- |
| `blogId` | string | Sí | Identificador del blog (en la ruta). |
| `estado` | string | No | `pending`, `processing`, `succeeded`, `failed` o `needs_attention`. |
| `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[].articleId` | string | Artículo enviado. |
| `data[].provider` | string | `wordpress` o `custom` (tu propia API). |
| `data[].status` | string | `pending` (por enviar), `processing`, `succeeded`, `failed` o `needs_attention` (el resultado es dudoso y requiere que lo revises). |
| `data[].remoteUrl` | string | URL del artículo en el destino. Solo si se conoce. |
| `data[].lastAttemptAt` | string | Fecha del último intento, ISO 8601 en UTC. |
| `data[].attempts` | integer | Intentos realizados. |
| `data[].error` | string | Texto del último error. Solo en `failed` y `needs_attention`. |

> **Nota:** Si el blog no tiene configurada la publicación externa, `data` llega vacío con `total: 0`. La lista cubre los 1000 artículos con actividad más reciente.

**Petición**

*curl*

```bash
curl "https://api.blogelia.com/v1/blogs/66f1c0a2b3c4d5e6f7a8b9c0/publications?estado=failed" \
  -H "Authorization: Bearer $BLOGENIA_API_KEY"
```

*Node.js*

```js
const res = await fetch('https://api.blogelia.com/v1/blogs/66f1c0a2b3c4d5e6f7a8b9c0/publications?estado=failed', {
  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/publications",
    params={"estado": "failed"},
    headers={"Authorization": "Bearer " + os.environ["BLOGENIA_API_KEY"]},
    timeout=10,
)
res.raise_for_status()
print(res.json())
```

**Respuesta**

```json
{
  "data": [
    {
      "articleId": "66f1c0a2b3c4d5e6f7a8b9c1",
      "provider": "custom",
      "status": "failed",
      "lastAttemptAt": "2026-11-03T09:02:10.000Z",
      "attempts": 3,
      "error": "El destino respondió con 502"
    }
  ],
  "page": 1,
  "limit": 20,
  "total": 1
}
```

**Respuestas**

| Código | Cuándo |
| --- | --- |
| `200` | Respuesta devuelta. |
| `400` | Parámetro no válido (por ejemplo, un `estado` desconocido) 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). |

## Entregas de webhooks

**`GET /webhooks/deliveries`**: Entregas de los webhooks de la organización, de la más reciente a la más antigua.

**Parámetros de consulta**

| Nombre | Tipo | Obligatorio | Descripción |
| --- | --- | --- | --- |
| `estado` | string | No | `pending`, `delivered` o `failed`. |
| `desde` | string | No | Día `YYYY-MM-DD` (Europe/Madrid), incluido: solo entregas creadas desde ese día. |
| `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 entrega. |
| `data[].webhookId` | string | Webhook al que se envió. |
| `data[].event` | string | Evento, por ejemplo `article.published`. |
| `data[].status` | string | `pending`, `delivered` o `failed`. |
| `data[].attempts` | integer | Intentos realizados. |
| `data[].lastStatusCode` | integer | Código HTTP de la última respuesta de tu servidor. Solo si hubo respuesta. |
| `data[].createdAt` | string | Fecha de creación, ISO 8601 en UTC. |

> **Nota:** Nunca se devuelve el cuerpo enviado ni el secreto del webhook.

**Petición**

*curl*

```bash
curl "https://api.blogelia.com/v1/webhooks/deliveries?estado=failed&desde=2026-11-01" \
  -H "Authorization: Bearer $BLOGENIA_API_KEY"
```

*Node.js*

```js
const res = await fetch('https://api.blogelia.com/v1/webhooks/deliveries?estado=failed&desde=2026-11-01', {
  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/webhooks/deliveries",
    params={"estado": "failed", "desde": "2026-11-01"},
    headers={"Authorization": "Bearer " + os.environ["BLOGENIA_API_KEY"]},
    timeout=10,
)
res.raise_for_status()
print(res.json())
```

**Respuesta**

```json
{
  "data": [
    {
      "id": "66f1c0a2b3c4d5e6f7a8b9d0",
      "webhookId": "66f1c0a2b3c4d5e6f7a8b9d1",
      "event": "article.published",
      "status": "failed",
      "attempts": 5,
      "lastStatusCode": 500,
      "createdAt": "2026-11-03T09:00:03.120Z"
    }
  ],
  "page": 1,
  "limit": 20,
  "total": 1
}
```

**Respuestas**

| Código | Cuándo |
| --- | --- |
| `200` | Respuesta devuelta. |
| `400` | Parámetro no válido (estado desconocido, `desde` que no es una fecha real) o parámetro desconocido. |
| `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). |
