Saltar al contenido

Guía para desarrolladores

Envía publicaciones a Amaposta desde tu propio flujo de contenido: sube archivos multimedia, crea una publicación para cada canal, prográmala y recibe un aviso cuando esté publicada.

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:

Comprobar una clave
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:

PermisoPermite
content:readLeer proyectos, canales, publicaciones y archivos multimedia.
content:writeSubir archivos multimedia y crear o editar publicaciones.
posts:publishProgramar 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.

Listar proyectos y canales
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.

POST /api/v1/projects/{project_id}/media
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.

Tres solicitudes
# 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.

POST /api/v1/projects/{project_id}/media/import
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.

Crear una publicación de vídeo y añadirla a la cola
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

CampoQué hace
kindtext, image o video. Obligatorio.
titleLo usan las plataformas con campo de título: YouTube, publicaciones de fotos de TikTok y vídeos de Facebook.
descriptionEl pie de foto, la descripción o el texto de la publicación. También se acepta como body, caption o text.
hashtagsUna lista, con o sin #.
media_id, media_urlEl 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_urlUna imagen de portada para vídeos, si la plataforma la admite.
channelsLos ID de los canales donde publicar. Omítelos para usar todos los canales activos del proyecto que admitan este tipo de publicación.
overridesCambios por canal, identificados por el ID del canal: title, description, hashtags y options de la plataforma, como privacy de YouTube.
schedulemode 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_refTu propia referencia, que se devuelve en la publicación y en las entregas de API personalizada.
metadataJSON 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.

Programar para una hora
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}.

Lo que viene y una publicación en detalle
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[]
"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.

422 Unprocessable Entity
{
  "error": {
    "code": "validation",
    "message": "Please fix the highlighted fields.",
    "fields": {
      "kind": "Use text, image or video."
    }
  }
}
EstadoCódigoSignificado
400bad_requestLa solicitud no se puede realizar tal como se pide, como al programar una publicación que no supera las comprobaciones.
401unauthorizedFalta la clave de API, es incorrecta o se ha revocado.
402plan_limitEl plan del espacio de trabajo no permite esto; limit indica el límite afectado.
403forbiddenA la clave le falta un permiso o acceso al proyecto.
404not_foundNo existe ese proyecto, publicación, archivo multimedia o endpoint.
409conflictEl cambio entra en conflicto con el estado actual, como al editar una publicación que ya se está publicando.
422validationHay que corregir uno o varios campos; consulta fields.
429rate_limitedMás de 120 solicitudes por minuto con esta clave.
502provider_errorUna 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.

EventoSe envía cuando
post.createdSe ha creado una publicación (panel o API)
post.scheduledSe ha programado una publicación
post.approvedSe ha aprobado una publicación
post.publishedSe ha publicado contenido en un canal
post.failedNo se ha podido publicar una publicación en un canal
channel.connectedSe ha conectado o reconectado un canal
channel.disconnectedUn canal ha perdido la conexión
media.readyHa terminado el procesamiento de un archivo subido
media.failedNo se ha podido procesar un archivo subido
post.published
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.

Node.js
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);
}
Python
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:

Lo que recibe tu endpoint
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" }
}

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.
Un receptor en Express
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.
Instagram 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.
Facebook 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.