Autenticación
4 permisosCada petición lleva una clave de API como bearer token. Las claves son para servidores. Si lo que quieres es conectar un asistente en vez de escribir código, usa el servidor MCP: se autentica en el navegador y no necesita ninguna clave.
Obtener una clave
Se crea desde los ajustes de tu cuenta. La clave completa se muestra una sola vez, al crearla, y después no se puede recuperar porque solo se guarda su hash. Si la pierdes, crea otra.
Authorization: Bearer ak_a1b2c3d4e5f6a7b8_<secret>
Las claves tienen la forma ak_<id>_<secreto>. La parte <id> es pública e identifica la clave en tus ajustes; la parte secreta es la que nunca debe acabar en un repositorio, en un log ni en un navegador.
Permisos
Una clave lleva permisos. write, publish y media implican read. write y publish son independientes: crear un borrador necesita write, y crear una publicación que de verdad se enviará necesita publish. Las claves creadas desde ajustes llevan los cuatro permisos; todavía no se pueden restringir ahí.
| Permiso | Permite |
|---|---|
| read | Leer publicaciones y cuentas conectadas. |
| write | Crear borradores y eliminar o cancelar publicaciones. |
| publish | Crear una publicación que saldrá de verdad, ya o programada. Basta por sí solo. |
| media | Reservado para endpoints de medios. Ningún endpoint lo comprueba todavía, así que concederlo no cambia nada hoy. |
Crear publicaciones
POST /v1/postsUna petición crea una publicación por cada cuenta de destino. Cada una es independiente, con su propia programación y su propio resultado; solo comparten un id de campaña.
curl https://antwork.io/api/v1/posts \
-H "Authorization: Bearer $ANTWORK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"accounts": ["acc_linkedin", "acc_x"],
"text": "Shipped the API today.",
"scheduledFor": "2026-10-01T09:00:00Z"
}'Cuerpo de la petición
| Campo | Significado |
|---|---|
| accounts | Obligatorio. Ids de cuenta de GET /api/v1/accounts, como máximo 25. Se crea una publicación por cuenta. |
| text | El texto de la publicación, usado en cada cuenta que no tenga entrada en texts. |
| texts | Texto por cuenta, indexado por id de cuenta. Sustituye a text para esa cuenta. Úsalo cuando una plataforma necesite una versión más corta. |
| media | URLs de medios de Antwork, no cualquiera. Un archivo tiene que estar en tu biblioteca antes de adjuntarlo; una URL externa se rechaza con media_not_hosted en lugar de aceptarse y fallar al publicar. Todavía no se pueden subir archivos por la API. |
| scheduledFor | Hora ISO 8601 de publicación, como máximo a 30 días. Omítela para publicar de inmediato. |
| draft | true crea un borrador y no envía nada, y solo necesita el permiso write. Cualquier otro valor crea una publicación que se enviará, lo que necesita publish. |
| options | Ajustes específicos de cada plataforma, indexados por plataforma. Consulta Opciones por plataforma. |
| workspaceId | El espacio de trabajo desde el que publicar. Solo es obligatorio si tienes más de un espacio de trabajo: sin él la petición se rechaza con workspace_required. Todas las cuentas deben pertenecer a él. |
| campaignId | Agrupa las publicaciones bajo un id. Se asigna solo cuando se acepta más de una cuenta; pasa un id existente para añadir estas publicaciones a ese grupo. |
platform_not_available, y el resto de la petición sigue adelante. GET /api/v1/accounts lista todas las cuentas conectadas, así que comprueba platform antes de enviar.La respuesta es 202 Accepted, nunca 200. Nada se publica dentro de la petición: la publicación se encola y un worker la envía, así que la respuesta dice qué se ha aceptado, no qué se ha publicado.
{
"object": "post_batch",
"campaignId": "b3f1...",
"posts": [
{
"object": "post_result",
"id": "pQ7x...",
"accountId": "acc_linkedin",
"platform": "linkedin",
"status": "accepted",
"reason": null,
"message": null
},
{
"object": "post_result",
"id": null,
"accountId": "acc_x",
"platform": "x",
"status": "rejected",
"reason": "text_too_long",
"message": "@antwork: Text exceeds x character limit"
}
]
}Qué significa cada estado
accepted— creada y programada (o guardada, si es un borrador). Consulta el id para ver qué pasa.rejected— rechazada antes de escribir nada. No existe; corrigereasony vuelve a enviar.failed— se creó pero no se pudo programar. Existe y no va a salir.
202 puede contener rechazos. Lee todas las entradas de posts, no solo el código de estado. Solo cuando no se acepta ninguna la respuesta es un 400, y el array viaja igualmente con ella.Programación
Omite scheduledFor para publicar de inmediato. Indica una hora ISO 8601 para programar, como máximo a 30 días. La cola que hay detrás no acepta tareas más lejanas, y el error indica la última hora que admite. Una hora en el pasado se rechaza con scheduled_for_in_past.
Consultar una publicación
GET /v1/posts/{id}La llamada de creación devuelve ids; aquí es donde ves qué ha sido de ellos. Consulta en vez de esperar en la petición de creación, que responde mucho antes de que la plataforma conteste.
{
"object": "post",
"id": "pQ7x...",
"workspaceId": "ws_...",
"accountId": "acc_linkedin",
"platform": "linkedin",
"campaignId": "b3f1...",
"status": "published",
"text": "Shipped the API today.",
"mediaUrls": [],
"scheduledFor": "2026-10-01T09:00:00.000Z",
"publishedAt": "2026-10-01T09:00:04.000Z",
"publishedUrl": "https://www.linkedin.com/feed/update/...",
"platformPostId": "urn:li:share:...",
"error": null,
"createdAt": "2026-09-22T10:00:00.000Z",
"updatedAt": "2026-10-01T09:00:04.000Z"
}Una vez ha salido, se rellenan publishedUrl y platformPostId. Si ha fallado, error.message dice qué contestó la plataforma y error.advice qué hacer al respecto.
Una publicación que no existe, es de otra persona o se ha eliminado responde 404 con post_not_found. Necesita el permiso read.
Estados de una publicación
draft— guardada, sin programar. No se enviará.scheduled— esperando su hora, o enviándose ahora mismo.published— publicada en la plataforma.publishedUrlyplatformPostIdestán rellenos.failed— no se publicó.errorexplica por qué.
Listar publicaciones
GET /api/v1/posts es la vía de recuperación: la única otra forma de llegar a una publicación es el id que devolvió la creación. De la más reciente a la más antigua, en todos tus espacios de trabajo. Filtro opcional status (uno de los cuatro de arriba), limit de 1 a 100 (por defecto 25) y cursor con el nextCursor de la página anterior. Un nextCursor nulo significa que no hay más.
curl "https://antwork.io/api/v1/posts?status=scheduled&limit=25" \
-H "Authorization: Bearer $ANTWORK_API_KEY"
{ "object": "list", "posts": [ ... ], "nextCursor": "MTc5MDA3..." }Listar cuentas
GET /v1/accountsLa primera llamada de cualquier integración, porque crear una publicación requiere ids de cuenta y no hay otra forma de conocerlos.
{
"object": "list",
"accounts": [
{
"object": "account",
"id": "acc_linkedin",
"workspaceId": "ws_...",
"platform": "linkedin",
"name": "Iker on LinkedIn",
"handle": "iker",
"accountType": "personal",
"active": true,
"tokenHealth": "healthy",
"connectedAt": "2026-01-05T09:00:00.000Z"
}
]
}Filtra con ?workspaceId= o ?platform=.
La lista incluye todas las cuentas conectadas, también de plataformas en las que la API aún no puede publicar. Solo las cuentas de LinkedIn y X (Twitter) sirven en accounts al crear una publicación.
tokenHealth antes de publicar. Una conexión que necesita reautorizarse falla en el momento de publicar, que puede ser días después de la petición que la programó.Cancelar y borrar
DELETE /v1/posts/{id}DELETE /api/v1/posts/{id} cancela una publicación programada o elimina un borrador. Una publicación ya publicada se borra de forma lógica para conservar su histórico de engagement, y en cualquier caso desaparece de todas las lecturas de esta API. Necesita el permiso write.
La publicación en la plataforma se queda como está salvo que lo pidas. ?deleteFromPlatform=true encola además el borrado de la publicación en vivo, que es irreversible y visible para otras personas. Quitar una publicación de Antwork y borrarla de LinkedIn son intenciones distintas, así que la API no lo adivina.
{
"object": "post_deleted",
"id": "pQ7x...",
"softDeleted": true,
"platformDeletionQueuedFor": ["linkedin"]
}softDeleted: true en la respuesta significa que el documento sobrevive para analítica, no que la publicación siga visible. platformDeletionQueuedFor lista las plataformas para las que se encoló un borrado, que ocurre en segundo plano igual que la publicación.Opciones por plataforma
Todo lo específico de una plataforma va en options, indexado por plataforma y aplicado a todas las cuentas de esa plataforma en la petición. Solo las plataformas de esta tabla aceptan opciones por la API.
| Plataforma | Acepta |
|---|---|
| no acepta opciones | |
| x | communityId, shareWithFollowers |
{
"accounts": ["acc_x"],
"text": "Shipped the API today.",
"options": {
"x": { "communityId": "1493446837214", "shareWithFollowers": true }
}
}400 que nombra el campo, no una publicación que sale sin el ajuste que pediste.Idempotencia
Idempotency-KeyEnvía una cabecera Idempotency-Key en cada creación. Sin ella, un timeout de red seguido del reintento que tu cliente HTTP hace por su cuenta produce una segunda publicación.
La clave se recuerda 24 horas, asociada a tu cuenta y al cuerpo exacto de la petición.
- Misma clave, mismo cuerpo, ya terminada: se devuelve la respuesta original y no se crea nada nuevo.
- Misma clave mientras la primera petición sigue en curso:
409. Reintenta en un momento. - Misma clave, cuerpo distinto:
422. Devolver la primera respuesta para otra petición sería peor que rechazarla.
Límites de uso
10.000/díaCada clave permite 10.000 peticiones al día y 120 por minuto. Los topes existen para frenar un bucle descontrolado, no para racionar el uso normal. Si te acercas a alguno, escríbenos en vez de sortearlo.
X-RateLimit-Limit: 10000 X-RateLimit-Remaining: 9812 X-RateLimit-Reset: 1790035200 Retry-After: 37
Todas las respuestas llevan el presupuesto actual, no solo las rechazadas, así que nunca hace falta agotar el límite para conocerlo. X-RateLimit-Reset es un timestamp Unix; Retry-After solo aparece en un 429 y va en segundos.
Errores
Todos los fallos tienen la misma forma. type es la clase sobre la que ramificar, code es el identificador estable del problema concreto y param nombra el campo culpable cuando lo hay. Trata message como texto para una persona leyendo un log: puede cambiar.
{
"error": {
"type": "invalid_request_error",
"code": "scheduled_for_too_far",
"message": "Posts can be scheduled at most 30 days ahead. The latest accepted time is 2026-10-22T10:00:00.000Z.",
"param": "scheduledFor"
}
}| Tipo | Significa |
|---|---|
| authentication_error | La clave falta, está mal formada, no existe o está revocada. Siempre 401; code indica cuál. |
| permission_error | La clave es válida pero le falta el permiso, o la cuenta detrás no tiene suscripción activa. 403. |
| invalid_request_error | Algo de la petición está mal. 400, o 404 para una publicación que no existe en tu cuenta. |
| rate_limit_error | Se alcanzó un tope. 429, con Retry-After. |
| api_error | Algo se rompió de nuestro lado. 500. Se puede reintentar con la misma clave de idempotencia. |
Ramifica sobre code, nunca sobre message. Los códigos son parte del contrato; las frases no.
Códigos de error
| Código | Estado y significado |
|---|---|
| api_key_missing | 401 — falta la cabecera Authorization: Bearer. |
| api_key_malformed | 401 — el valor no tiene forma de clave de Antwork. |
| api_key_unknown | 401 — no existe ninguna clave con este id y secreto. |
| api_key_revoked | 401 — la clave se revocó en ajustes. |
| insufficient_scope | 403 — la clave no tiene el permiso que necesita esta llamada. |
| subscription_inactive | 403 — la cuenta de la clave no tiene una suscripción activa. |
| invalid_body | 400 — el cuerpo no es un objeto JSON. |
| accounts_required | 400 — falta accounts o está vacío. |
| too_many_accounts | 400 — más cuentas de las que admite una petición. |
| invalid_text | 400 — text no es una cadena. |
| invalid_texts | 400 — texts no es un objeto de cadenas indexado por id de cuenta. |
| invalid_media | 400 — media no es un array de cadenas. |
| media_not_hosted | 400 — una URL de media no está en tu biblioteca de Antwork. |
| invalid_scheduled_for | 400 — scheduledFor no es una hora ISO 8601 válida. |
| scheduled_for_in_past | 400 — scheduledFor está en el pasado. |
| scheduled_for_too_far | 400 — scheduledFor supera el horizonte de programación. |
| workspace_required | 400 — tienes varios espacios de trabajo y no enviaste workspaceId. |
| workspace_not_found | 400 — workspaceId no es un espacio de trabajo tuyo. |
| invalid_option | 400 — una clave de options es desconocida, tiene un tipo incorrecto o es de una plataforma que no está en la petición. |
| no_targets_accepted | 400 — no se aceptó ninguna cuenta y ninguna dio un motivo más concreto. |
| invalid_status | 400 — el filtro status no es uno de los cuatro estados. |
| invalid_limit | 400 — limit no es un número entero dentro del rango. |
| invalid_cursor | 400 — cursor no lo emitió esta API. |
| post_not_found | 404 — no existe esa publicación en tu cuenta. |
| idempotency_in_flight | 409 — una petición con esta Idempotency-Key sigue en curso. |
| idempotency_key_reused | 422 — esta Idempotency-Key se usó con otro cuerpo. |
| rate_limit_burst | 429 — por encima del límite por minuto. |
| rate_limit_daily | 429 — por encima del límite diario. |
| internal_error | 500 — algo falló por nuestra parte. |
Cuando no se acepta ninguna cuenta de una petición de creación, la respuesta es un 400 cuyo code es el motivo de rechazo de la primera cuenta (ver la tabla siguiente), con el array posts completo al lado.
Por qué se rechazó una cuenta
El reason de una entrada rejected o failed en la respuesta de creación.
| Código | Estado y significado |
|---|---|
| account_unresolved | El id no es una cuenta conectada en este espacio de trabajo. |
| platform_not_available | La cuenta es de una plataforma en la que la API aún no publica. |
| text_empty | No hay texto para esta cuenta y la plataforma lo necesita. |
| text_too_long | El texto supera el límite de caracteres de la plataforma. |
| media_required | La plataforma necesita una imagen o un vídeo y no se adjuntó ninguno. |
| unsupported_media | La plataforma no acepta uno de los tipos de archivo adjuntos. |
| video_too_long | El vídeo es más largo de lo que permite la plataforma. |
| token_invalid | La conexión de la cuenta ha caducado. Vuelve a conectarla en Antwork. |
| token_refresh_failed | No se pudo renovar la conexión. Vuelve a conectarla en Antwork. |
| quota_exceeded | Has agotado las publicaciones de tu plan en este ciclo. |
| schedule_failed | La publicación se creó pero no se pudo poner en cola (failed). Existe y no se enviará. |