Autenticación
Crea una clave de API en el panel, en Ajustes, API y webhooks. Las claves empiezan por ss_live_, pertenecen a un espacio de trabajo y pueden limitarse a determinados proyectos. Envía la clave como token bearer en cada solicitud:
curl https://www.amaposta.com/api/v1/me \
-H "Authorization: Bearer ss_live_…"
La respuesta indica el nombre de la clave, sus permisos y el espacio de trabajo. Cada clave tiene uno o varios permisos:
| Permiso | Permite |
|---|---|
content:read | Leer proyectos, canales, publicaciones y archivos multimedia. |
content:write | Subir archivos multimedia y crear o editar publicaciones. |
posts:publish | Programar y publicar sin revisión. Sin este permiso, las publicaciones pasan a la bandeja de revisión en los proyectos que usan aprobaciones y se mantienen como borradores en los demás. |
Cada clave puede hacer 120 solicitudes por minuto. Si se supera el límite, recibes 429 con el código rate_limited; espera un momento y vuelve a intentarlo.
Proyectos y canales
Las publicaciones pertenecen a un proyecto. Lista los proyectos que tu clave puede ver y después los canales de uno de ellos. Necesitarás el ID de un canal para dirigirle una publicación o personalizar su texto.
curl https://www.amaposta.com/api/v1/projects \
-H "Authorization: Bearer $AMAPOSTA_KEY"
curl https://www.amaposta.com/api/v1/projects/$PROJECT_ID/channels \
-H "Authorization: Bearer $AMAPOSTA_KEY"
Un canal tiene un provider (youtube, instagram, tiktok, x, facebook, custom o sandbox), un status (active, expiring, needs_reconnect, error o disabled) y los tipos de publicación que supports.
Subir archivos multimedia
Las imágenes y los vídeos se suben una vez por proyecto y se pueden usar en tantas publicaciones como quieras. Hay tres formas de añadirlos.
Subida directa
Envía el archivo como datos de formulario multipart en un campo llamado file. Ideal para archivos de hasta unos cientos de MB.
curl https://www.amaposta.com/api/v1/projects/$PROJECT_ID/media \
-H "Authorization: Bearer $AMAPOSTA_KEY" \
-F "[email protected]"
Subida prefirmada para archivos grandes
Solicita una URL de subida, envía los bytes directamente al almacenamiento con PUT y después avisa a Amaposta de que has terminado.
# 1. Ask for an upload URL. The response is {"media": { … }, "upload_url": "…"}
curl https://www.amaposta.com/api/v1/projects/$PROJECT_ID/media/uploads \
-H "Authorization: Bearer $AMAPOSTA_KEY" \
-H "Content-Type: application/json" \
-d '{"filename": "batch-142.mp4", "content_type": "video/mp4", "size_bytes": 48213844}'
# 2. Upload the bytes to upload_url
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: video/mp4" \
--upload-file batch-142.mp4
# 3. Finish the upload
curl -X POST https://www.amaposta.com/api/v1/media/$MEDIA_ID/complete \
-H "Authorization: Bearer $AMAPOSTA_KEY"
Importar desde una URL
Proporciona a Amaposta una URL pública y descargará el archivo en segundo plano. También puedes omitir este paso y pasar media_url al crear la publicación.
curl https://www.amaposta.com/api/v1/projects/$PROJECT_ID/media/import \
-H "Authorization: Bearer $AMAPOSTA_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://cdn.example.com/batch-142.mp4"}'
Cada subida devuelve un objeto multimedia. Su status pasa de pending a processing y después a ready (o failed, con un error). Una vez procesado, incluye width, height, duration_ms y una miniatura. Consulta GET /api/v1/media/{id} o escucha el webhook media.ready. La url que devuelve está firmada y es válida durante 12 horas.
Crear una publicación
POST /api/v1/projects/{project_id}/posts crea una publicación con una entrega por canal. Amaposta la adapta a cada plataforma: los títulos solo se envían donde hay un campo de título; los hashtags se convierten en etiquetas en YouTube y en texto integrado en las demás.
curl https://www.amaposta.com/api/v1/projects/$PROJECT_ID/posts \
-H "Authorization: Bearer $AMAPOSTA_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: episode-142" \
-d @- <<'JSON'
{
"kind": "video",
"title": "How we roast a batch in 12 minutes",
"description": "From green beans to first crack. We filmed one batch start to finish.",
"hashtags": ["coffee", "behindthescenes"],
"media_url": "https://cdn.example.com/batch-142.mp4",
"cover_url": "https://cdn.example.com/batch-142-cover.jpg",
"overrides": {
"<x-channel-id>": {
"description": "Batch 142 is out: green beans to first crack in 12 minutes."
},
"<youtube-channel-id>": {
"options": { "privacy": "unlisted" }
}
},
"schedule": { "mode": "queue" },
"external_ref": "episode-142",
"metadata": {
"pipeline": { "name": "nightly-cut", "run": "2026-10-17" }
}
}
JSON
Campos
| Campo | Qué hace |
|---|---|
kind | text, image o video. Obligatorio. |
title | Lo usan las plataformas con campo de título: YouTube, publicaciones de fotos de TikTok y vídeos de Facebook. |
description | El pie de foto, la descripción o el texto de la publicación. También se acepta como body, caption o text. |
hashtags | Una lista, con o sin #. |
media_id, media_url | El ID de un archivo multimedia subido o una URL pública desde la que Amaposta lo descarga. Necesario para publicaciones con imágenes o vídeos. |
cover_media_id, cover_url | Una imagen de portada para vídeos, si la plataforma la admite. |
channels | Los ID de los canales donde publicar. Omítelos para usar todos los canales activos del proyecto que admitan este tipo de publicación. |
overrides | Cambios por canal, identificados por el ID del canal: title, description, hashtags y options de la plataforma, como privacy de YouTube. |
schedule | mode es at (con un valor at en ISO 8601), now o queue para la siguiente franja de publicación libre. Omítelo para crear un borrador. |
external_ref | Tu propia referencia, que se devuelve en la publicación y en las entregas de API personalizada. |
metadata | JSON de estructura libre guardado con la publicación. Incluye "pipeline" para marcar el contenido generado; el panel lo muestra y los webhooks indican el origen como pipeline. |
La respuesta es la publicación, con una entrada en deliveries por canal.
Reintentos seguros
Las redes pierden solicitudes. Envía una cabecera Idempotency-Key al crear una publicación y un reintento con la misma clave devolverá la publicación del primer intento en lugar de crear otra. Usa un identificador estable de tu sistema, como el ID de una tarea o episodio. Las claves se recortan a 200 caracteres.
Programar y publicar
Programa un borrador, publícalo ahora, añádelo a la cola o retíralo del calendario con POST /api/v1/posts/{post_id}/schedule.
curl https://www.amaposta.com/api/v1/posts/$POST_ID/schedule \
-H "Authorization: Bearer $AMAPOSTA_KEY" \
-H "Content-Type: application/json" \
-d '{"mode": "at", "at": "2026-10-20T07:00:00Z"}'
atPublica a la hora indicada, con hasta dos años de antelación.nowPublica de inmediato.queueOcupa la siguiente franja de publicación libre para los canales de la publicación en las próximas 12 semanas.unscheduleDevuelve la publicación a los borradores.
Antes de programar se ejecutan las mismas comprobaciones que en el editor. Si alguna falla, la solicitud devuelve 400 con un mensaje como Corrige esto antes de programar: YouTube necesita un título.
Para programar se necesita el permiso posts:publish o un proyecto con las aprobaciones activadas; en ese caso, la publicación espera en la bandeja de revisión.
Leer publicaciones y entregas
Lista las publicaciones de un proyecto, filtradas por status (estados separados por comas, o inbox, queue o history), un intervalo from/to, limit y offset. La respuesta es {"items": […], "total": n}.
curl "https://www.amaposta.com/api/v1/projects/$PROJECT_ID/posts?status=queue" \
-H "Authorization: Bearer $AMAPOSTA_KEY"
curl https://www.amaposta.com/api/v1/posts/$POST_ID \
-H "Authorization: Bearer $AMAPOSTA_KEY"
Una publicación tiene un status general (draft, in_review, changes_requested, ready, scheduled, publishing, published, partial o failed) y una entrega por canal:
"deliveries": [
{
"id": "0192b7d4-…",
"channel_id": "0192b7a1-…",
"status": "published",
"url": "https://www.youtube.com/watch?v=…",
"error": null,
"published_at": "2026-10-20T07:00:41Z"
},
{
"id": "0192b7d4-…",
"channel_id": "0192b7a9-…",
"status": "failed",
"url": null,
"error": {
"code": "endpoint_unavailable",
"message": "Your endpoint returned 503 Service Unavailable",
"hint": "Amaposta will retry with backoff."
},
"published_at": null
}
]
Los estados de entrega son pending, scheduled, publishing, processing, published, failed y cancelled. DELETE /api/v1/posts/{post_id} elimina una publicación.
Errores
Todos los errores tienen la misma estructura: un objeto error con un code estable, un message que puedes mostrar a las personas y, a veces, fields (mensajes por campo) y un hint.
{
"error": {
"code": "validation",
"message": "Please fix the highlighted fields.",
"fields": {
"kind": "Use text, image or video."
}
}
}
| Estado | Código | Significado |
|---|---|---|
| 400 | bad_request | La solicitud no se puede realizar tal como se pide, como al programar una publicación que no supera las comprobaciones. |
| 401 | unauthorized | Falta la clave de API, es incorrecta o se ha revocado. |
| 402 | plan_limit | El plan del espacio de trabajo no permite esto; limit indica el límite afectado. |
| 403 | forbidden | A la clave le falta un permiso o acceso al proyecto. |
| 404 | not_found | No existe ese proyecto, publicación, archivo multimedia o endpoint. |
| 409 | conflict | El cambio entra en conflicto con el estado actual, como al editar una publicación que ya se está publicando. |
| 422 | validation | Hay que corregir uno o varios campos; consulta fields. |
| 429 | rate_limited | Más de 120 solicitudes por minuto con esta clave. |
| 502 | provider_error | Una plataforma ha rechazado la solicitud o ha fallado; hint indica qué hacer. |
Webhooks
Añade un endpoint en Ajustes, API y webhooks, elige los eventos que quieras y copia su secreto de firma. Amaposta envía JSON por POST a tu URL para cada evento. Cualquier respuesta que no sea 2xx, o la falta de respuesta en 15 segundos, se reintenta con esperas crecientes; los endpoints que siguen fallando se desactivan y se avisa a los administradores.
| Evento | Se envía cuando |
|---|---|
post.created | Se ha creado una publicación (panel o API) |
post.scheduled | Se ha programado una publicación |
post.approved | Se ha aprobado una publicación |
post.published | Se ha publicado contenido en un canal |
post.failed | No se ha podido publicar una publicación en un canal |
channel.connected | Se ha conectado o reconectado un canal |
channel.disconnected | Un canal ha perdido la conexión |
media.ready | Ha terminado el procesamiento de un archivo subido |
media.failed | No se ha podido procesar un archivo subido |
POST /your/webhook HTTP/1.1
Content-Type: application/json
User-Agent: Amaposta-Webhooks/1.0
X-Amaposta-Event: post.published
X-Amaposta-Event-Id: 0192b7e0-…
X-Amaposta-Signature: t=1792479641,v1=5f2b0c…
{
"id": "0192b7e0-…",
"type": "post.published",
"created_at": "2026-10-20T07:00:41Z",
"project_id": "0192a3f1-…",
"data": {
"post_id": "0192b7c8-…",
"delivery_id": "0192b7d4-…",
"channel_id": "0192b7a1-…",
"provider": "youtube",
"external_id": "…",
"url": "https://www.youtube.com/watch?v=…"
}
}
Verificar la firma
X-Amaposta-Signature es t=<unix seconds>,v1=<hex>, donde v1 es HMAC_SHA256(secret, t + "." + body) sobre el cuerpo sin procesar de la solicitud. Calcúlalo, compara en tiempo constante y rechaza las marcas de tiempo antiguas para impedir ataques de repetición.
import crypto from "node:crypto";
// rawBody: the request body exactly as received (string or Buffer).
export function verifySignature(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest();
const given = Buffer.from(parts.v1 ?? "", "hex");
return given.length === expected.length && crypto.timingSafeEqual(given, expected);
}
import hashlib
import hmac
import time
def verify_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
if abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
Canales de API personalizada
Un canal de API personalizada publica en un endpoint HTTPS que gestionas tú: tu CMS, una herramienta de boletines o una plataforma que Amaposta aún no admite. Aparece en el calendario y tiene comprobaciones, aprobaciones y reintentos como cualquier otro canal. Cuando se publica contenido, Amaposta envía un POST por entrega:
POST /amaposta/publish HTTP/1.1
Authorization: Bearer <your API key>
Content-Type: application/json
Idempotency-Key: 0192b7d4-…
X-Amaposta-Delivery: 0192b7d4-…
X-Amaposta-Signature: t=1792479600,v1=9ac41e…
{
"id": "0192b7d4-…",
"type": "post.publish",
"created_at": "2026-10-20T07:00:00Z",
"content": {
"kind": "video",
"title": "How we roast a batch in 12 minutes",
"text": "From green beans to first crack. We filmed one batch start to finish.",
"hashtags": ["coffee", "behindthescenes"],
"media": {
"url": "https://…",
"content_type": "video/mp4",
"size_bytes": 48213844,
"width": 1080,
"height": 1920,
"duration_ms": 58000,
"filename": "batch-142.mp4"
},
"cover": null,
"options": {}
},
"post": {
"id": "0192b7c8-…",
"external_ref": "episode-142",
"scheduled_at": "2026-10-20T07:00:00Z",
"metadata": {
"pipeline": { "name": "nightly-cut" }
}
},
"project": { "id": "0192a3f1-…", "name": "Spring launch" },
"channel": { "id": "0192b7a9-…", "name": "api.example.com" }
}
Authorization- Tu clave de API. El nombre de la cabecera y el prefijo (
Bearerde forma predeterminada) se configuran en el canal. Idempotency-Key- El ID de la entrega. Un reintento de la misma entrega envía la misma clave para que puedas omitir el trabajo ya realizado.
X-Amaposta-Delivery- El ID de la entrega de nuevo, para tus registros.
X-Amaposta-Signature- Se firma como los webhooks, con el secreto de firma del propio canal.
Cómo responder
- 2xxPublicado. Puedes devolver
{"id": "…", "url": "…"}y el enlace aparecerá en la publicación. - 401, 403Se ha rechazado la clave. Se marca el canal para reconexión y se avisa a los administradores. No uses estos códigos para una firma incorrecta.
- 5xx, 408, 429Temporal. Amaposta reintenta con esperas crecientes.
- Otros 4xxLa publicación se marca como fallida con tu mensaje y alguien puede corregirla y pulsar Reintentar.
app.post("/amaposta/publish", express.raw({ type: "application/json" }), async (req, res) => {
const signature = req.get("X-Amaposta-Signature") ?? "";
if (!verifySignature(req.body, signature, process.env.AMAPOSTA_SIGNING_SECRET)) {
return res.status(400).json({ message: "Bad signature" });
}
const delivery = JSON.parse(req.body);
const item = await publishToYourSystem(delivery.content, req.get("Idempotency-Key"));
res.json({ id: item.id, url: item.url });
});
Tu endpoint debe ser accesible desde internet; no se siguen redirecciones. Al conectar el canal, Amaposta lo comprueba mediante GET a tu URL de estado, si has establecido una, o mediante una solicitud OPTIONS al endpoint.
Canales Sandbox
Un canal Sandbox se comporta como uno real, pero nada sale de Amaposta. Úsalo para crear y probar tu integración de principio a fin: las publicaciones se programan, entran en emisión, se publican o fallan y activan los mismos webhooks. Los canales Sandbox son gratuitos en todos los planes y no cuentan para el límite de canales.
Elige cómo se comporta al añadirlo:
- Funcionar siempreLa publicación siempre se realiza.
- Fallar de vez en cuandoVe los fallos y reintentos sin esperar a una interrupción real.
- Fallar siemprePrueba cómo gestionas
post.failed. - Conexión caducadaVe cómo son las alertas y los enlaces de reconexión cuando caduca el acceso.
Reglas de las plataformas
Lo que acepta cada tipo de canal. Las comprobaciones usan los mismos límites, así que la mayoría de los problemas aparecen antes de programar una publicación, en lugar de al publicarla.
| Plataforma | Publicación | Título | Texto | Hashtags | Multimedia |
|---|---|---|---|---|---|
| YouTube | Vídeo | TítuloObligatorio, hasta 100 caracteres | DescripciónHasta 5000 caracteres | Se envían como etiquetas, hasta 60 | MP4, MOV, WebM, AVI, MKV; hasta 256 GB; de 1 s a 12 h |
| Las cuentas que no han verificado un número de teléfono pueden subir vídeos de hasta 15 minutos y no pueden establecer miniaturas personalizadas. Cada subida consume cuota de la API; la cuota predeterminada de Google permite unas seis subidas diarias por aplicación. | |||||
| Imagen | Sin título | Pie de fotoHasta 2200 caracteres | Se añaden al texto, hasta 30 | JPEG; hasta 8 MB | |
| Vídeo | Sin título | Pie de fotoHasta 2200 caracteres | Se añaden al texto, hasta 30 | MP4, MOV; hasta 300 MB; de 3 s a 15 min | |
| Requiere una cuenta de empresa o de creador de Instagram. Instagram permite 50 publicaciones mediante la API por cuenta en un periodo móvil de 24 horas. No se pueden hacer publicaciones de solo texto en Instagram. | |||||
| TikTok | Imagen | TítuloHasta 90 caracteres | DescripciónHasta 4000 caracteres | Se añade al texto | JPEG, WebP; hasta 20 MB |
| Vídeo | Sin título | Pie de fotoHasta 2200 caracteres | Se añade al texto | MP4, MOV, WebM; hasta 4 GB; de 3 s a 10 min | |
| Las publicaciones de aplicaciones que no han completado la auditoría de TikTok solo son visibles para su creador. TikTok elige la portada de un fotograma del vídeo; no admite imágenes de portada personalizadas. No se pueden hacer publicaciones de solo texto en TikTok. | |||||
| X | Texto | Sin título | PublicaciónObligatorio, hasta 280 (los enlaces cuentan como 23 y los emojis como 2) | Se añade al texto | Ninguno |
| Imagen | Sin título | PublicaciónHasta 280 (los enlaces cuentan como 23 y los emojis como 2) | Se añade al texto | JPEG, PNG, WebP, GIF; hasta 5 MB | |
| Vídeo | Sin título | PublicaciónHasta 280 (los enlaces cuentan como 23 y los emojis como 2) | Se añade al texto | MP4, MOV; hasta 512 MB; de 0,5 s a 2:20 min | |
| Con tu propia aplicación de desarrollador de X, las publicaciones consumen sus créditos de API de pago por uso. Los hashtags cuentan para el límite de caracteres. | |||||
| Texto | Sin título | PublicaciónObligatorio, hasta 63.206 caracteres | Se añade al texto | Ninguno | |
| Imagen | Sin título | Pie de fotoHasta 63.206 caracteres | Se añade al texto | JPEG, PNG, GIF, WebP; hasta 10 MB | |
| Vídeo | TítuloHasta 255 caracteres | DescripciónHasta 63.206 caracteres | Se añade al texto | MP4, MOV; hasta 1 GB; de 1 s a 20 min | |
| Solo publica en páginas; no se puede publicar en perfiles personales mediante la API. | |||||
| API personalizada | Texto | TítuloSin límite de longitud | DescripciónSin límite de longitud | Se envían como etiquetas | Ninguno |
| Imagen | TítuloSin límite de longitud | DescripciónSin límite de longitud | Se envían como etiquetas | JPEG, PNG, WebP, GIF | |
| Vídeo | TítuloSin límite de longitud | DescripciónSin límite de longitud | Se envían como etiquetas | MP4, MOV, WebM | |
| Cada solicitud se firma con una cabecera HMAC-SHA256 para que puedas verificar que procede de Amaposta. Responde con 2xx y, opcionalmente, JSON {"id": "…", "url": "…"}. Las respuestas 408, 429 y 5xx se reintentan con esperas crecientes; 401/403 marcan el canal para reconexión; los demás 4xx hacen fallar la entrega para que puedas corregirla y reintentar. | |||||
| Sandbox | Texto | TítuloHasta 200 caracteres | DescripciónHasta 5000 caracteres | Se añaden al texto, hasta 30 | Ninguno |
| Imagen | TítuloHasta 200 caracteres | DescripciónHasta 5000 caracteres | Se añaden al texto, hasta 30 | JPEG, PNG, WebP, GIF | |
| Vídeo | TítuloHasta 200 caracteres | DescripciónHasta 5000 caracteres | Se añaden al texto, hasta 30 | MP4, MOV, WebM | |
| La publicación tarda un momento para que puedas ver el cambio de estado en directo. | |||||