Saltar para o conteúdo

Guia de programação

Envia publicações para Amaposta a partir do teu próprio fluxo de conteúdos: carrega ficheiros multimédia, cria uma publicação para todos os canais, agenda-a e recebe a confirmação quando sair.

Autenticação

Cria uma chave de API no painel em Definições, API e webhooks. As chaves começam por ss_live_, pertencem a um espaço de trabalho e podem ser limitadas a determinados projetos. Envia a chave como token bearer em cada pedido:

Verificar uma chave
curl https://www.amaposta.com/api/v1/me \
  -H "Authorization: Bearer ss_live_…"

A resposta indica o nome da chave, as suas permissões e o espaço de trabalho. Cada chave tem uma ou mais permissões:

ÂmbitoPermite
content:readLer projetos, canais, publicações e ficheiros multimédia.
content:writeCarregar ficheiros multimédia e criar ou editar publicações.
posts:publishAgendar e publicar sem revisão. Sem esta permissão, as publicações vão para a caixa de revisão nos projetos com aprovações e mantêm-se como rascunhos nos restantes.

Cada chave pode fazer 120 pedidos por minuto. Acima desse limite, recebes 429 com o código rate_limited; espera um momento e tenta novamente.

Projetos e canais

As publicações pertencem a um projeto. Lista os projetos que a tua chave pode ver e depois os canais de um deles. Precisas do ID de um canal para o selecionar ou substituir o respetivo texto.

Listar projetos e canais
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"

Um canal tem um provider (youtube, instagram, tiktok, x, facebook, custom ou sandbox), um status (active, expiring, needs_reconnect, error ou disabled) e os tipos de publicação que supports.

Carregar ficheiros multimédia

As imagens e os vídeos são carregados uma vez por projeto e podem ser usados em qualquer número de publicações. Há três formas de os adicionar.

Carregamento direto

Envia o ficheiro como dados de formulário multipart num campo chamado file. Ideal para ficheiros até algumas centenas 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]"

Carregamento pré-assinado para ficheiros grandes

Pede um URL de carregamento, envia os bytes diretamente para o armazenamento com PUT e informa Amaposta de que terminaste.

Três pedidos
# 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 de um URL

Dá a Amaposta um URL público para descarregar o ficheiro em segundo plano. Também podes ignorar este passo e passar media_url ao criar a publicação.

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 carregamento devolve um objeto multimédia. O seu status passa de pending para processing e para ready (ou failed, com um error). Depois de processado, inclui width, height, duration_ms e uma miniatura. Consulta periodicamente GET /api/v1/media/{id} ou escuta o webhook media.ready. O url devolvido é assinado e válido durante 12 horas.

Criar uma publicação

POST /api/v1/projects/{project_id}/posts cria uma publicação com uma entrega por canal. Amaposta adapta-a a cada plataforma: os títulos só são enviados quando existe um campo de título, as hashtags tornam-se etiquetas no YouTube e texto integrado nas restantes.

Criar uma publicação de vídeo e adicioná-la à fila
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

CampoO que faz
kindtext, image ou video. Obrigatório.
titleUsado pelas plataformas com um campo de título: YouTube, publicações de fotografias do TikTok, vídeos do Facebook.
descriptionA legenda, a descrição ou o texto da publicação. Também aceite como body, caption ou text.
hashtagsUma lista, com ou sem #.
media_id, media_urlUm ID de ficheiro multimédia carregado ou um URL público que Amaposta descarrega por ti. Necessário para publicações de imagem e vídeo.
cover_media_id, cover_urlUma imagem de capa para vídeos, quando a plataforma o permite.
channelsIDs dos canais onde publicar. Omite-os para usar todos os canais ativos do projeto que suportam este tipo de publicação.
overridesAlterações por canal, indexadas pelo ID do canal: title, description, hashtags e options da plataforma, como privacy do YouTube.
schedulemode é at (com um at em ISO 8601), now ou queue para o próximo horário livre de publicação. Omite-o para criar um rascunho.
external_refA tua própria referência, devolvida na publicação e nas entregas de API personalizada.
metadataJSON de formato livre guardado com a publicação. Inclui "pipeline" para identificar conteúdo gerado; o painel apresenta-o e os webhooks indicam a origem como pipeline.

A resposta é a publicação, incluindo uma entrada em deliveries por canal.

Novas tentativas seguras

As redes perdem pedidos. Envia um cabeçalho Idempotency-Key ao criar uma publicação e uma nova tentativa com a mesma chave devolve a publicação da primeira tentativa em vez de criar outra. Usa um valor estável do teu lado, como um ID de tarefa ou episódio. As chaves são truncadas a 200 carateres.

Agendar e publicar

Agenda um rascunho, publica-o agora, adiciona-o à fila ou retira-o do calendário com POST /api/v1/posts/{post_id}/schedule.

Agendar para uma 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 à hora indicada, com até dois anos de antecedência.
  • nowPublica de imediato.
  • queueUsa o próximo horário de publicação livre dos canais da publicação nas próximas 12 semanas.
  • unscheduleMove a publicação de volta para os rascunhos.

Antes de qualquer agendamento, são executadas as mesmas verificações do editor. Se alguma falhar, o pedido devolve 400 com uma mensagem como Corrige isto antes de agendar: o YouTube precisa de um título. O agendamento exige a permissão posts:publish ou um projeto com aprovações ativadas; nesse caso, a publicação aguarda na caixa de revisão.

Ler publicações e entregas

Lista as publicações de um projeto, filtradas por status (estados separados por vírgulas ou inbox, queue ou history), um intervalo from/to, limit e offset. A resposta é {"items": […], "total": n}.

O que vem a seguir e uma publicação em detalhe
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"

Uma publicação tem um status global (draft, in_review, changes_requested, ready, scheduled, publishing, published, partial ou failed) e uma 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
  }
]

Os estados de entrega são pending, scheduled, publishing, processing, published, failed e cancelled. DELETE /api/v1/posts/{post_id} remove uma publicação.

Erros

Todos os erros têm a mesma estrutura: um objeto error com um code estável, uma message que podes mostrar às pessoas e, por vezes, fields (mensagens por campo) e uma hint.

422 Unprocessable Entity
{
  "error": {
    "code": "validation",
    "message": "Please fix the highlighted fields.",
    "fields": {
      "kind": "Use text, image or video."
    }
  }
}
EstadoCódigoSignificado
400bad_requestO pedido não pode ser executado como solicitado, como agendar uma publicação que falha as verificações.
401unauthorizedA chave de API está em falta, incorreta ou revogada.
402plan_limitO plano do espaço de trabalho não permite isto; limit indica o limite.
403forbiddenFalta à chave uma permissão ou acesso ao projeto.
404not_foundEsse projeto, publicação, ficheiro multimédia ou endpoint não existe.
409conflictA alteração entra em conflito com o estado atual, como editar uma publicação que já está a ser publicada.
422validationUm ou mais campos precisam de correção; consulta fields.
429rate_limitedMais de 120 pedidos por minuto com esta chave.
502provider_errorUma plataforma recusou ou falhou; hint indica o que fazer.

Webhooks

Adiciona um endpoint em Definições, API e webhooks, escolhe os eventos e copia o segredo de assinatura. Amaposta envia JSON por POST para o teu URL para cada evento. Qualquer resposta diferente de 2xx ou a ausência de resposta em 15 segundos origina novas tentativas com intervalos crescentes; os endpoints que continuam a falhar são desativados com um aviso aos administradores.

EventoEnviado quando
post.createdUma publicação foi criada (painel ou API)
post.scheduledUma publicação foi agendada
post.approvedUma publicação foi aprovada
post.publishedUma publicação ficou disponível num canal
post.failedUma publicação falhou num canal
channel.connectedUm canal foi ligado ou novamente ligado
channel.disconnectedUm canal perdeu a ligação
media.readyO processamento de um carregamento terminou
media.failedNão foi possível processar um carregamento
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 a assinatura

X-Amaposta-Signature é t=<unix seconds>,v1=<hex>, em que v1 é HMAC_SHA256(secret, t + "." + body) calculado sobre o corpo original do pedido. Calcula-o, compara em tempo constante e rejeita carimbos de data/hora antigos para impedir repetições.

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", ""))

Canais de API personalizada

Um canal de API personalizada publica num endpoint HTTPS que geres: o teu CMS, uma ferramenta de newsletters, uma plataforma que Amaposta ainda não abrange. Aparece no calendário e tem verificações, aprovações e novas tentativas como qualquer outro canal. Quando uma publicação sai, Amaposta envia um POST por entrega:

O que o teu endpoint recebe
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" }
}

Como responder

  • 2xxPublicada. Podes devolver {"id": "…", "url": "…"} e a ligação aparece na publicação.
  • 401, 403A chave foi rejeitada. O canal é assinalado para voltar a ligar e os administradores são informados. Não uses estes códigos para uma assinatura inválida.
  • 5xx, 408, 429Temporário. Amaposta tenta novamente com intervalos crescentes.
  • Outros 4xxA publicação é marcada como falhada com a tua mensagem e alguém pode corrigi-la e premir Tentar novamente.
Um recetor em 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 });
});

O teu endpoint tem de estar acessível pela Internet; os redirecionamentos não são seguidos. Ao ligares o canal, Amaposta verifica-o com GET para o teu URL de funcionamento, se o definires, ou com um pedido OPTIONS para o endpoint.

Canais Sandbox

Um canal Sandbox comporta-se como um canal real, mas nada sai de Amaposta. Usa-o para criar e testar a tua integração de ponta a ponta: as publicações são agendadas, entram no ar, são publicadas ou falham e acionam os mesmos webhooks. Os canais Sandbox são gratuitos em todos os planos e não contam para os limites de canais.

Escolhe como se comporta ao adicioná-lo:

  • Ter sempre sucessoTodas as publicações são publicadas.
  • Falhar de vez em quandoVê as falhas e as novas tentativas sem esperar por uma interrupção real.
  • Falhar sempreTesta o teu tratamento de post.failed.
  • Ligação expiradaVê os alertas e as ligações para restabelecer o acesso quando este expira.

Regras da plataforma

O que cada tipo de canal aceita. As verificações usam os mesmos limites, para a maioria dos problemas surgir antes do agendamento da publicação e não quando é publicada.

Plataforma Publicação Título Texto Hashtags Multimédia
YouTube Vídeo TítuloObrigatório, até 100 carateres DescriçãoAté 5000 carateres Enviadas como etiquetas, até 60 MP4, MOV, WebM, AVI, MKV; até 256 GB; 1 s a 12 h
As contas sem número de telefone verificado podem carregar até 15 minutos e não podem definir miniaturas personalizadas. Cada carregamento consome quota da API; a quota predefinida do Google permite cerca de seis carregamentos por dia por aplicação.
Instagram Imagem Sem título LegendaAté 2200 carateres Adicionadas ao texto, até 30 JPEG; até 8 MB
Vídeo Sem título LegendaAté 2200 carateres Adicionadas ao texto, até 30 MP4, MOV; até 300 MB; 3 s a 15 min
Exige uma conta Instagram Business ou Creator. O Instagram permite 50 publicações pela API por conta em cada período móvel de 24 horas. Não são possíveis publicações apenas de texto no Instagram.
TikTok Imagem TítuloAté 90 carateres DescriçãoAté 4000 carateres Adicionadas ao texto JPEG, WebP; até 20 MB
Vídeo Sem título LegendaAté 2200 carateres Adicionadas ao texto MP4, MOV, WebM; até 4 GB; 3 s a 10 min
As publicações de aplicações que não concluíram a auditoria do TikTok só são visíveis para o criador. O TikTok escolhe a capa a partir de um fotograma do vídeo; não são suportadas imagens de capa personalizadas. Não são possíveis publicações apenas de texto no TikTok.
X Texto Sem título PublicaçãoObrigatório, até 280 (as ligações contam como 23, os emojis como 2) Adicionadas ao texto Nenhum
Imagem Sem título PublicaçãoAté 280 (as ligações contam como 23, os emojis como 2) Adicionadas ao texto JPEG, PNG, WebP, GIF; até 5 MB
Vídeo Sem título PublicaçãoAté 280 (as ligações contam como 23, os emojis como 2) Adicionadas ao texto MP4, MOV; até 512 MB; 0,5 s a 2:20 min
Com a tua própria aplicação de programador do X, as publicações consomem os seus créditos da API, pagos por utilização. As hashtags contam para o limite de carateres.
Facebook Texto Sem título PublicaçãoObrigatório, até 63 206 carateres Adicionadas ao texto Nenhum
Imagem Sem título LegendaAté 63 206 carateres Adicionadas ao texto JPEG, PNG, GIF, WebP; até 10 MB
Vídeo TítuloAté 255 carateres DescriçãoAté 63 206 carateres Adicionadas ao texto MP4, MOV; até 1 GB; 1 s a 20 min
Publica apenas em Páginas; não é possível publicar em perfis pessoais através da API.
API personalizada Texto TítuloSem limite de duração DescriçãoSem limite de duração Enviadas como etiquetas Nenhum
Imagem TítuloSem limite de duração DescriçãoSem limite de duração Enviadas como etiquetas JPEG, PNG, WebP, GIF
Vídeo TítuloSem limite de duração DescriçãoSem limite de duração Enviadas como etiquetas MP4, MOV, WebM
Cada pedido é assinado com um cabeçalho HMAC-SHA256 para poderes verificar que veio de Amaposta. Responde com 2xx e, opcionalmente, JSON {"id": "…", "url": "…"}. As respostas 408, 429 e 5xx originam novas tentativas com intervalos crescentes; 401/403 assinalam o canal para voltar a ligar; outros 4xx fazem a entrega falhar para poderes corrigir e tentar novamente.
Sandbox Texto TítuloAté 200 carateres DescriçãoAté 5000 carateres Adicionadas ao texto, até 30 Nenhum
Imagem TítuloAté 200 carateres DescriçãoAté 5000 carateres Adicionadas ao texto, até 30 JPEG, PNG, WebP, GIF
Vídeo TítuloAté 200 carateres DescriçãoAté 5000 carateres Adicionadas ao texto, até 30 MP4, MOV, WebM
A publicação demora um momento para poderes acompanhar a mudança de estado em tempo real.