Autenticazione
Crea una chiave API nel pannello, in Impostazioni, API e webhook. Le chiavi iniziano con ss_live_, appartengono a uno spazio di lavoro e possono essere limitate a determinati progetti. Invia la chiave come token bearer in ogni richiesta:
curl https://www.amaposta.com/api/v1/me \
-H "Authorization: Bearer ss_live_…"
La risposta indica il nome della chiave, i suoi ambiti e lo spazio di lavoro. Ogni chiave ha uno o più ambiti:
| Ambito | Consente |
|---|---|
content:read | Leggi progetti, canali, post e contenuti multimediali. |
content:write | Carica contenuti multimediali e crea o modifica post. |
posts:publish | Programma e pubblica senza revisione. Senza questa autorizzazione, i post passano nella casella di revisione nei progetti che usano le approvazioni e rimangono bozze in tutti gli altri. |
Ogni chiave può effettuare 120 richieste al minuto. Oltre questo limite ricevi 429 con il codice rate_limited; attendi un momento e riprova.
Progetti e canali
I post appartengono a un progetto. Elenca i progetti visibili alla tua chiave, poi i canali di uno di essi. Ti servirà l'ID di un canale per sceglierlo come destinazione o personalizzarne il testo.
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 canale ha un provider (youtube, instagram, tiktok, x, facebook, custom o sandbox), uno status (active, expiring, needs_reconnect, error o disabled) e i tipi di post che supports.
Carica contenuti multimediali
Immagini e video vengono caricati una sola volta per progetto e possono essere usati in qualsiasi numero di post. Ci sono tre modalità di caricamento.
Caricamento diretto
Invia il file come dati multipart di un modulo in un campo chiamato file. Ideale per file fino a qualche centinaio di MB.
curl https://www.amaposta.com/api/v1/projects/$PROJECT_ID/media \
-H "Authorization: Bearer $AMAPOSTA_KEY" \
-F "[email protected]"
Caricamento prefirmato per file di grandi dimensioni
Richiedi un URL di caricamento, invia i byte direttamente all'archiviazione con PUT, poi comunica ad Amaposta che hai terminato.
# 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"
Importa da un URL
Fornisci ad Amaposta un URL pubblico e scaricherà il file in background. Puoi anche saltare questo passaggio e passare media_url quando crei il post.
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"}'
Ogni caricamento restituisce un oggetto multimediale. Il suo status passa da pending a processing e poi a ready (o failed, con un error). Una volta elaborato contiene width, height, duration_ms e una miniatura. Interroga periodicamente GET /api/v1/media/{id} o ascolta il webhook media.ready. L'url restituito è firmato e valido per 12 ore.
Crea un post
POST /api/v1/projects/{project_id}/posts crea un post con una pubblicazione per ogni canale. Amaposta lo adatta a ogni piattaforma: i titoli vanno solo dove esiste il relativo campo, gli hashtag diventano tag su YouTube e testo in linea altrove.
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
Campi
| Campo | Cosa fa |
|---|---|
kind | text, image o video. Obbligatorio. |
title | Usato dalle piattaforme con un campo titolo: YouTube, post fotografici TikTok, video Facebook. |
description | La didascalia, la descrizione o il testo del post. Accettato anche come body, caption o text. |
hashtags | Un elenco, con o senza #. |
media_id, media_url | L'ID di un contenuto multimediale caricato, oppure un URL pubblico da cui Amaposta lo scarica per te. Necessario per i post con immagini e video. |
cover_media_id, cover_url | Un'immagine di copertina per i video, dove la piattaforma la supporta. |
channels | ID dei canali su cui pubblicare. Omettilo per usare tutti i canali attivi del progetto che supportano questo tipo di post. |
overrides | Modifiche per canale indicizzate per ID canale: title, description, hashtags e options della piattaforma, ad esempio privacy di YouTube. |
schedule | mode è at (con un valore at ISO 8601), now o queue per la prossima fascia di pubblicazione libera. Omettilo per creare una bozza. |
external_ref | Un tuo riferimento, riportato nel post e nelle pubblicazioni API personalizzata. |
metadata | JSON a struttura libera salvato con il post. Includi "pipeline" per contrassegnare i contenuti generati; il pannello lo mostra e i webhook riportano l'origine come pipeline. |
La risposta è il post, con una voce in deliveries per canale.
Nuovi tentativi sicuri
Le reti possono perdere richieste. Invia un header Idempotency-Key quando crei un post: un nuovo tentativo con la stessa chiave restituisce il post del primo tentativo invece di crearne un secondo. Usa un valore stabile dal tuo sistema, ad esempio l'ID di un'attività o di un episodio. Le chiavi vengono troncate a 200 caratteri.
Programma e pubblica
Programma una bozza, pubblicala ora, aggiungila alla coda o rimuovila dal 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"}'
atPubblica all'orario indicato, fino a due anni in anticipo.nowPubblica subito.queueUsa la prossima fascia di pubblicazione libera per i canali del post nelle prossime 12 settimane.unscheduleRiporta il post tra le bozze.
Prima di programmare qualsiasi post, vengono eseguiti gli stessi controlli dell'editor. Se uno fallisce, la richiesta restituisce 400 con un messaggio come Risolvi prima di programmare: YouTube richiede un titolo.
Per programmare occorre l'ambito posts:publish oppure un progetto con le approvazioni attive; in quest'ultimo caso il post attende nella casella di revisione.
Leggi post e pubblicazioni
Elenca i post di un progetto, filtrati per status (stati separati da virgole, oppure inbox, queue o history), intervallo from/to, limit e offset. La risposta è {"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"
Un post ha uno status complessivo (draft, in_review, changes_requested, ready, scheduled, publishing, published, partial o failed) e una pubblicazione per canale:
"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
}
]
Gli stati di pubblicazione sono pending, scheduled, publishing, processing, published, failed e cancelled. DELETE /api/v1/posts/{post_id} rimuove un post.
Errori
Ogni errore ha la stessa struttura: un oggetto error con un code stabile, un message da mostrare alle persone e, talvolta, fields (messaggi per campo) e un hint.
{
"error": {
"code": "validation",
"message": "Please fix the highlighted fields.",
"fields": {
"kind": "Use text, image or video."
}
}
}
| Stato | Codice | Significato |
|---|---|---|
| 400 | bad_request | La richiesta non può essere eseguita così com'è, ad esempio programmare un post che non supera i controlli. |
| 401 | unauthorized | La chiave API è mancante, errata o revocata. |
| 402 | plan_limit | Il piano dello spazio di lavoro non lo consente; limit indica quale limite. |
| 403 | forbidden | Alla chiave manca un ambito o l'accesso al progetto. |
| 404 | not_found | Progetto, post, contenuto multimediale o endpoint inesistente. |
| 409 | conflict | La modifica è incompatibile con lo stato attuale, ad esempio la modifica di un post già in pubblicazione. |
| 422 | validation | Uno o più campi richiedono correzioni; consulta fields. |
| 429 | rate_limited | Più di 120 richieste al minuto su questa chiave. |
| 502 | provider_error | Una piattaforma ha rifiutato la richiesta o ha riscontrato un errore; hint indica cosa fare. |
Webhook
Aggiungi un endpoint in Impostazioni, API e webhook, scegli gli eventi desiderati e copia il relativo segreto di firma. Amaposta invia JSON al tuo URL tramite POST per ogni evento. Qualsiasi risposta diversa da 2xx, o l'assenza di risposta entro 15 secondi, attiva nuovi tentativi a intervalli crescenti. Gli endpoint che continuano a fallire vengono disattivati e gli amministratori ricevono un avviso.
| Evento | Inviato quando |
|---|---|
post.created | Un post è stato creato (pannello o API) |
post.scheduled | Un post è stato programmato |
post.approved | Un post è stato approvato |
post.published | Un post è andato online su un canale |
post.failed | Un post non è stato pubblicato su un canale |
channel.connected | Un canale è stato collegato o ricollegato |
channel.disconnected | Un canale ha perso la connessione |
media.ready | L'elaborazione di un file caricato è terminata |
media.failed | Non è stato possibile elaborare un file caricato |
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=…"
}
}
Verifica la firma
X-Amaposta-Signature è t=<unix seconds>,v1=<hex>, dove v1 è HMAC_SHA256(secret, t + "." + body) calcolato sul corpo grezzo della richiesta. Calcolalo, confrontalo in tempo costante e rifiuta i timestamp obsoleti per impedire attacchi di replay.
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", ""))
Canali API personalizzata
Un canale API personalizzata pubblica su un endpoint HTTPS gestito da te: il tuo CMS, uno strumento per newsletter o una piattaforma non ancora supportata da Amaposta. Compare nel calendario e riceve controlli, approvazioni e nuovi tentativi come qualsiasi altro canale. Quando un post viene pubblicato, Amaposta invia un POST per ogni pubblicazione:
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- La tua chiave API. Il nome dell'header e il prefisso (
Bearerper impostazione predefinita) vengono impostati sul canale. Idempotency-Key- L'ID della consegna. Un nuovo tentativo della stessa consegna invia la stessa chiave, così puoi saltare il lavoro già svolto.
X-Amaposta-Delivery- Di nuovo l'ID della consegna, per i tuoi registri.
X-Amaposta-Signature- Firmato come i webhook, con il segreto di firma del canale.
Come rispondere
- 2xxPubblicato. Puoi restituire
{"id": "…", "url": "…"}per mostrare il link sul post. - 401, 403La chiave è stata rifiutata. Il canale viene contrassegnato come da ricollegare e gli amministratori vengono avvisati. Non usare questi stati per una firma errata.
- 5xx, 408, 429Temporaneo. Amaposta riprova a intervalli crescenti.
- Altri 4xxIl post viene contrassegnato come non riuscito con il tuo messaggio e qualcuno può correggerlo e premere Riprova.
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 });
});
Il tuo endpoint deve essere raggiungibile da Internet; i reindirizzamenti non vengono seguiti. Quando colleghi il canale, Amaposta lo verifica con GET all'URL di controllo dello stato, se impostato, oppure con una richiesta OPTIONS all'endpoint.
Canali sandbox
Un canale sandbox si comporta come uno reale, ma nulla lascia Amaposta. Usalo per sviluppare e testare la tua integrazione dall'inizio alla fine: i post vengono programmati, vanno online, vengono pubblicati o falliscono e attivano gli stessi webhook. I canali sandbox sono gratuiti in ogni piano e non incidono sui limiti dei canali.
Scegli come si comporta quando lo aggiungi:
- Riesce sempreOgni post viene pubblicato.
- Fallisce di tanto in tantoOsserva errori e nuovi tentativi senza attendere un'interruzione reale.
- Fallisce sempreVerifica la tua gestione di
post.failed. - Connessione scadutaScopri come appaiono gli avvisi e i link di riconnessione quando l'accesso scade.
Regole delle piattaforme
Cosa accetta ogni tipo di canale. I controlli usano gli stessi limiti, quindi la maggior parte dei problemi emerge prima che un post venga programmato anziché al momento della pubblicazione.
| Piattaforma | Post | Titolo | Testo | Hashtag | Contenuti multimediali |
|---|---|---|---|---|---|
| YouTube | Video | TitoloObbligatorio, fino a 100 caratteri | DescrizioneFino a 5000 caratteri | Inviati come tag, fino a 60 | MP4, MOV, WebM, AVI, MKV; fino a 256 GB; da 1 s a 12 h |
| Gli account che non hanno verificato un numero di telefono possono caricare video fino a 15 minuti e non possono impostare miniature personalizzate. Ogni caricamento consuma quota API; la quota predefinita di Google consente circa sei caricamenti al giorno per app. | |||||
| Immagine | Senza titolo | DidascaliaFino a 2200 caratteri | Aggiunti al testo, fino a 30 | JPEG; fino a 8 MB | |
| Video | Senza titolo | DidascaliaFino a 2200 caratteri | Aggiunti al testo, fino a 30 | MP4, MOV; fino a 300 MB; da 3 s a 15 min | |
| Richiede un account Instagram Business o Creator. Instagram consente 50 post pubblicati tramite API per account nell'arco di 24 ore mobili. Su Instagram non sono possibili post di solo testo. | |||||
| TikTok | Immagine | TitoloFino a 90 caratteri | DescrizioneFino a 4000 caratteri | Aggiunti al testo | JPEG, WebP; fino a 20 MB |
| Video | Senza titolo | DidascaliaFino a 2200 caratteri | Aggiunti al testo | MP4, MOV, WebM; fino a 4 GB; da 3 s a 10 min | |
| I post delle app che non hanno completato la verifica di TikTok sono visibili solo al creatore. TikTok sceglie la copertina da un fotogramma del video; le immagini di copertina personalizzate non sono supportate. Su TikTok non sono possibili post di solo testo. | |||||
| X | Testo | Senza titolo | PostObbligatorio, fino a 280 (i link contano 23, le emoji 2) | Aggiunti al testo | Nessuno |
| Immagine | Senza titolo | PostFino a 280 (i link contano 23, le emoji 2) | Aggiunti al testo | JPEG, PNG, WebP, GIF; fino a 5 MB | |
| Video | Senza titolo | PostFino a 280 (i link contano 23, le emoji 2) | Aggiunti al testo | MP4, MOV; fino a 512 MB; da 0,5 s a 2:20 min | |
| Con la tua app sviluppatore X, i post usano i suoi crediti API a consumo. Gli hashtag rientrano nel limite di caratteri. | |||||
| Testo | Senza titolo | PostObbligatorio, fino a 63.206 caratteri | Aggiunti al testo | Nessuno | |
| Immagine | Senza titolo | DidascaliaFino a 63.206 caratteri | Aggiunti al testo | JPEG, PNG, GIF, WebP; fino a 10 MB | |
| Video | TitoloFino a 255 caratteri | DescrizioneFino a 63.206 caratteri | Aggiunti al testo | MP4, MOV; fino a 1 GB; da 1 s a 20 min | |
| Pubblica solo sulle Pagine; non è possibile pubblicare sui profili personali tramite API. | |||||
| API personalizzata | Testo | TitoloNessun limite di lunghezza | DescrizioneNessun limite di lunghezza | Inviati come tag | Nessuno |
| Immagine | TitoloNessun limite di lunghezza | DescrizioneNessun limite di lunghezza | Inviati come tag | JPEG, PNG, WebP, GIF | |
| Video | TitoloNessun limite di lunghezza | DescrizioneNessun limite di lunghezza | Inviati come tag | MP4, MOV, WebM | |
| Ogni richiesta è firmata con un header HMAC-SHA256, così puoi verificare che provenga da Amaposta. Rispondi con 2xx e, facoltativamente, JSON {"id": "…", "url": "…"}. Le risposte 408, 429 e 5xx vengono ritentate a intervalli crescenti; 401/403 contrassegnano il canale come da ricollegare; gli altri 4xx fanno fallire la pubblicazione, così puoi correggerla e riprovare. | |||||
| Sandbox | Testo | TitoloFino a 200 caratteri | DescrizioneFino a 5000 caratteri | Aggiunti al testo, fino a 30 | Nessuno |
| Immagine | TitoloFino a 200 caratteri | DescrizioneFino a 5000 caratteri | Aggiunti al testo, fino a 30 | JPEG, PNG, WebP, GIF | |
| Video | TitoloFino a 200 caratteri | DescrizioneFino a 5000 caratteri | Aggiunti al testo, fino a 30 | MP4, MOV, WebM | |
| La pubblicazione richiede un momento, così puoi vedere lo stato cambiare in tempo reale. | |||||