Authentifizierung
Erstelle im Dashboard unter Einstellungen, API und Webhooks einen API-Schlüssel. Schlüssel beginnen mit ss_live_, gehören zu genau einem Workspace und lassen sich auf bestimmte Projekte beschränken. Schicke den Schlüssel bei jeder Anfrage als Bearer-Token mit:
curl https://www.amaposta.com/api/v1/me \
-H "Authorization: Bearer ss_live_…"
Die Antwort nennt den Schlüssel, seine Berechtigungen und den Workspace. Jeder Schlüssel hat eine oder mehrere Berechtigungen:
| Berechtigung | Erlaubt |
|---|---|
content:read | Projekte, Kanäle, Beiträge und Medien lesen. |
content:write | Medien hochladen und Beiträge erstellen oder bearbeiten. |
posts:publish | Ohne Prüfung planen und veröffentlichen. Ohne diese Berechtigung landen Beiträge in Projekten mit Freigaben in der Freigabe-Inbox und bleiben überall sonst Entwürfe. |
Jeder Schlüssel kann 120 Anfragen pro Minute stellen. Bei mehr Anfragen bekommst du 429 mit dem Code rate_limited; warte kurz und versuche es erneut.
Projekte und Kanäle
Beiträge gehören zu einem Projekt. Liste die Projekte auf, die dein Schlüssel sehen kann, und dann die Kanäle eines Projekts. Die ID eines Kanals brauchst du, um gezielt auf ihm zu posten oder seinen Text zu überschreiben.
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"
Ein Kanal hat einen provider (youtube, instagram, tiktok, x, facebook, custom oder sandbox), einen status (active, expiring, needs_reconnect, error oder disabled) und die Beitragsarten, die er unterstützt (supports).
Medien hochladen
Bilder und Videos lädst du einmal pro Projekt hoch und kannst sie dann in beliebig vielen Beiträgen verwenden. Dafür gibt es drei Wege.
Direkter Upload
Schicke die Datei als Multipart-Formulardaten in einem Feld namens file. Am besten für Dateien bis zu einigen hundert MB.
curl https://www.amaposta.com/api/v1/projects/$PROJECT_ID/media \
-H "Authorization: Bearer $AMAPOSTA_KEY" \
-F "[email protected]"
Presigned-Upload für große Dateien
Fordere eine Upload-URL an, lade die Bytes per PUT direkt in den Speicher und sag Amaposta dann, dass du fertig bist.
# 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"
Von einer URL importieren
Gib Amaposta eine öffentliche URL, dann wird die Datei im Hintergrund heruntergeladen. Du kannst diesen Schritt auch überspringen und beim Erstellen des Beitrags media_url mitgeben.
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"}'
Jeder Upload liefert ein Medienobjekt zurück. Sein status wechselt von pending über processing zu ready (oder zu failed, mit einem error). Nach der Verarbeitung enthält es width, height, duration_ms und ein Vorschaubild. Frage GET /api/v1/media/{id} regelmäßig ab oder warte auf den Webhook media.ready. Die zurückgegebene url ist signiert und 12 Stunden gültig.
Beitrag erstellen
POST /api/v1/projects/{project_id}/posts erstellt einen Beitrag mit einer Zustellung pro Kanal. Amaposta passt ihn an jede Plattform an: Titel gehen nur dorthin, wo es ein Titelfeld gibt, und Hashtags werden auf YouTube zu Tags und anderswo Teil des Texts.
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
Felder
| Feld | Funktion |
|---|---|
kind | text, image oder video. Pflichtfeld. |
title | Wird von Plattformen mit Titelfeld verwendet: YouTube, TikTok-Fotobeiträge, Facebook-Videos. |
description | Bildunterschrift, Beschreibung oder Beitragstext. Wird auch als body, caption oder text angenommen. |
hashtags | Eine Liste, mit oder ohne #. |
media_id, media_url | Die ID eines hochgeladenen Mediums oder eine öffentliche URL, von der Amaposta die Datei für dich lädt. Nötig für Bild- und Videobeiträge. |
cover_media_id, cover_url | Ein Titelbild für Videos, sofern die Plattform eines unterstützt. |
channels | IDs der Kanäle, auf denen gepostet wird. Lässt du das Feld weg, werden alle aktiven Kanäle des Projekts verwendet, die diese Beitragsart unterstützen. |
overrides | Änderungen pro Kanal, mit der Kanal-ID als Schlüssel: title, description, hashtags und plattformspezifische options wie privacy bei YouTube. |
schedule | mode ist at (mit einem ISO-8601-Zeitpunkt in at), now oder queue für den nächsten freien Sendeplatz. Lässt du das Feld weg, entsteht ein Entwurf. |
external_ref | Deine eigene Referenz; du bekommst sie im Beitrag und in den Zustellungen an Kanäle mit eigener API zurück. |
metadata | Beliebiges JSON, das mit dem Beitrag gespeichert wird. Mit "pipeline" kennzeichnest du generierte Inhalte; das Dashboard zeigt das an, und Webhooks melden als Herkunft pipeline. |
Die Antwort ist der Beitrag, mit einem Eintrag in deliveries pro Kanal.
Sichere Wiederholungen
Im Netz gehen Anfragen verloren. Schicke beim Erstellen eines Beitrags den Header Idempotency-Key mit: Eine Wiederholung mit demselben Schlüssel liefert dann den Beitrag aus dem ersten Versuch, statt einen zweiten anzulegen. Nimm etwas Stabiles aus deinem System, etwa eine Job- oder Episoden-ID. Schlüssel werden auf 200 Zeichen gekürzt.
Planen und veröffentlichen
Mit POST /api/v1/posts/{post_id}/schedule planst du einen Entwurf ein, veröffentlichst ihn sofort, stellst ihn in die Warteschlange oder nimmst ihn wieder aus dem Kalender.
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"}'
atVeröffentlicht zum angegebenen Zeitpunkt, bis zu zwei Jahre im Voraus.nowVeröffentlicht sofort.queueNimmt den nächsten freien Sendeplatz der Kanäle des Beitrags innerhalb der nächsten 12 Wochen.unscheduleMacht den Beitrag wieder zum Entwurf.
Bevor etwas geplant wird, laufen dieselben Checks wie im Editor. Schlägt einer fehl, antwortet die Anfrage mit 400 und einer Meldung wie Fix this before scheduling: YouTube needs a title.
Zum Planen brauchst du die Berechtigung posts:publish oder ein Projekt mit aktivierten Freigaben; dann wartet der Beitrag in der Freigabe-Inbox.
Beiträge und Zustellungen abrufen
Liste die Beiträge eines Projekts auf, gefiltert nach status (kommagetrennte Status oder inbox, queue bzw. history), einem Zeitfenster from/to sowie limit und offset. Die Antwort ist {"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"
Ein Beitrag hat einen Gesamtstatus in status (draft, in_review, changes_requested, ready, scheduled, publishing, published, partial oder failed) und eine Zustellung pro Kanal:
"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
}
]
Zustellungen haben die Status pending, scheduled, publishing, processing, published, failed und cancelled. DELETE /api/v1/posts/{post_id} löscht einen Beitrag.
Fehler
Alle Fehler haben dieselbe Form: ein error-Objekt mit einem stabilen code, einer message, die du Menschen anzeigen kannst, und manchmal fields (Meldungen pro Feld) und einem hint.
{
"error": {
"code": "validation",
"message": "Please fix the highlighted fields.",
"fields": {
"kind": "Use text, image or video."
}
}
}
| Status | Code | Bedeutung |
|---|---|---|
| 400 | bad_request | Die Anfrage lässt sich so nicht ausführen, etwa wenn ein Beitrag geplant werden soll, der seine Checks nicht besteht. |
| 401 | unauthorized | Der API-Schlüssel fehlt, ist falsch oder wurde widerrufen. |
| 402 | plan_limit | Der Tarif des Workspaces erlaubt das nicht; limit nennt das betroffene Limit. |
| 403 | forbidden | Dem Schlüssel fehlt eine Berechtigung oder der Zugriff auf das Projekt. |
| 404 | not_found | Projekt, Beitrag, Medium oder Endpunkt nicht gefunden. |
| 409 | conflict | Die Änderung passt nicht zum aktuellen Zustand, etwa beim Bearbeiten eines Beitrags, der gerade veröffentlicht wird. |
| 422 | validation | Ein oder mehrere Felder müssen korrigiert werden; siehe fields. |
| 429 | rate_limited | Mehr als 120 Anfragen pro Minute mit diesem Schlüssel. |
| 502 | provider_error | Eine Plattform hat abgelehnt oder ist ausgefallen; hint sagt, was zu tun ist. |
Webhooks
Lege unter Einstellungen, API und Webhooks einen Endpunkt an, wähle die gewünschten Ereignisse und kopiere sein Signatur-Secret. Amaposta schickt für jedes Ereignis einen POST mit JSON an deine URL. Kommt keine 2xx-Antwort oder innerhalb von 15 Sekunden gar keine, versucht Amaposta es mit zunehmendem Abstand erneut. Endpunkte, die dauerhaft fehlschlagen, werden abgeschaltet, und Admins bekommen eine Benachrichtigung.
| Ereignis | Gesendet, wenn |
|---|---|
post.created | Ein Beitrag wurde erstellt (Dashboard oder API) |
post.scheduled | Ein Beitrag wurde geplant |
post.approved | Ein Beitrag wurde freigegeben |
post.published | Ein Beitrag ist auf einem Kanal live gegangen |
post.failed | Ein Beitrag konnte auf einem Kanal nicht veröffentlicht werden |
channel.connected | Ein Kanal wurde verbunden oder neu verbunden |
channel.disconnected | Ein Kanal hat seine Verbindung verloren |
media.ready | Ein Upload wurde fertig verarbeitet |
media.failed | Ein Upload konnte nicht verarbeitet werden |
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=…"
}
}
Signatur prüfen
X-Amaposta-Signature hat die Form t=<Unix-Sekunden>,v1=<hex>. Dabei ist v1 gleich HMAC_SHA256(secret, t + "." + body) über den unveränderten Request-Body. Berechne den Wert selbst, vergleiche in konstanter Zeit und lehne alte Zeitstempel ab, um Replay-Angriffe zu verhindern.
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", ""))
Kanäle mit eigener API
Ein Kanal mit eigener API veröffentlicht an einen HTTPS-Endpunkt, den du betreibst: dein CMS, ein Newsletter-Tool oder eine Plattform, die Amaposta noch nicht abdeckt. Er steht im Kalender und bekommt Checks, Freigaben und Wiederholungen wie jeder andere Kanal. Geht ein Beitrag raus, schickt Amaposta einen POST pro Zustellung:
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- Dein API-Schlüssel. Header-Name und Präfix (standardmäßig
Bearer) legst du am Kanal fest. Idempotency-Key- Die ID der Zustellung. Eine Wiederholung derselben Zustellung sendet denselben Schlüssel, sodass du bereits erledigte Arbeit überspringen kannst.
X-Amaposta-Delivery- Noch einmal die ID der Zustellung, für deine Logs.
X-Amaposta-Signature- Signiert wie Webhooks, mit dem eigenen Signatur-Secret des Kanals.
So antwortest du
- 2xxVeröffentlicht. Gib optional
{"id": "…", "url": "…"}zurück, dann erscheint der Link am Beitrag. - 401, 403Der Schlüssel wurde abgelehnt. Der Kanal wird zum Neuverbinden markiert, und Admins werden benachrichtigt. Verwende diese Codes nicht für eine ungültige Signatur.
- 5xx, 408, 429Vorübergehend. Amaposta versucht es mit zunehmendem Abstand erneut.
- Andere 4xxDer Beitrag wird mit deiner Meldung als fehlgeschlagen markiert. Jemand aus dem Team kann ihn korrigieren und auf „Wiederholen“ klicken.
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 });
});
Dein Endpunkt muss aus dem Internet erreichbar sein; Weiterleitungen werden nicht verfolgt. Beim Verbinden des Kanals prüft Amaposta ihn mit einem GET an deine Health-URL, falls du eine angibst, sonst mit einer OPTIONS-Anfrage an den Endpunkt.
Sandbox-Kanäle
Ein Sandbox-Kanal verhält sich wie ein echter, aber nichts verlässt Amaposta. Damit baust und testest du deine Integration von Anfang bis Ende: Beiträge werden geplant, gehen auf Sendung, werden veröffentlicht oder schlagen fehl und lösen dieselben Webhooks aus. Sandbox-Kanäle sind in jedem Tarif kostenlos und zählen nicht zum Kanal-Limit.
Beim Hinzufügen legst du fest, wie er sich verhält:
- Immer erfolgreichJeder Beitrag wird veröffentlicht.
- Ab und zu fehlschlagenErlebe Fehler und Wiederholungen, ohne auf einen echten Ausfall zu warten.
- Immer fehlschlagenTeste, wie du mit
post.failedumgehst. - Abgelaufene VerbindungSieh dir an, wie Hinweise und Links zum Neuverbinden aussehen, wenn der Zugriff abläuft.
Plattformregeln
Was jeder Kanaltyp akzeptiert. Die Checks verwenden dieselben Limits, daher fallen die meisten Probleme schon vor dem Planen auf und nicht erst beim Veröffentlichen.
| Plattform | Beitrag | Titel | Text | Hashtags | Medien |
|---|---|---|---|---|---|
| YouTube | Video | TitelPflichtfeld, bis zu 100 Zeichen | BeschreibungBis zu 5.000 Zeichen | Werden als Tags gesendet, bis zu 60 | MP4, MOV, WebM, AVI, MKV; bis 256 GB; 1 Sek. bis 12 Std. |
| Konten ohne bestätigte Telefonnummer können bis zu 15 Minuten hochladen und kein eigenes Titelbild setzen. Jeder Upload verbraucht API-Kontingent; das Standardkontingent von Google reicht für etwa sechs Uploads pro Tag und App. | |||||
| Bild | Kein Titel | BildunterschriftBis zu 2.200 Zeichen | Werden an den Text angehängt, bis zu 30 | JPEG; bis 8 MB | |
| Video | Kein Titel | BildunterschriftBis zu 2.200 Zeichen | Werden an den Text angehängt, bis zu 30 | MP4, MOV; bis 300 MB; 3 Sek. bis 15 Min. | |
| Erfordert ein Instagram-Business- oder Creator-Konto. Instagram erlaubt 50 per API veröffentlichte Beiträge pro Konto innerhalb von 24 Stunden. Reine Textbeiträge sind auf Instagram nicht möglich. | |||||
| TikTok | Bild | TitelBis zu 90 Zeichen | BeschreibungBis zu 4.000 Zeichen | Werden an den Text angehängt | JPEG, WebP; bis 20 MB |
| Video | Kein Titel | BildunterschriftBis zu 2.200 Zeichen | Werden an den Text angehängt | MP4, MOV, WebM; bis 4 GB; 3 Sek. bis 10 Min. | |
| Beiträge aus Apps, die TikToks Prüfung noch nicht durchlaufen haben, sieht nur das Konto selbst. TikTok wählt das Titelbild aus einem Videobild; eigene Titelbilder werden nicht unterstützt. Reine Textbeiträge sind auf TikTok nicht möglich. | |||||
| X | Text | Kein Titel | BeitragPflichtfeld, bis zu 280 (Links zählen als 23, Emojis als 2) | Werden an den Text angehängt | Keine |
| Bild | Kein Titel | BeitragBis zu 280 (Links zählen als 23, Emojis als 2) | Werden an den Text angehängt | JPEG, PNG, WebP, GIF; bis 5 MB | |
| Video | Kein Titel | BeitragBis zu 280 (Links zählen als 23, Emojis als 2) | Werden an den Text angehängt | MP4, MOV; bis 512 MB; 0,5 Sek. bis 2:20 Min. | |
| Posten über die X-API erfordert einen kostenpflichtigen API-Tarif für deine Entwickler-App. Hashtags zählen zur Zeichenbegrenzung. | |||||
| Text | Kein Titel | BeitragPflichtfeld, bis zu 63.206 Zeichen | Werden an den Text angehängt | Keine | |
| Bild | Kein Titel | BildunterschriftBis zu 63.206 Zeichen | Werden an den Text angehängt | JPEG, PNG, GIF, WebP; bis 10 MB | |
| Video | TitelBis zu 255 Zeichen | BeschreibungBis zu 63.206 Zeichen | Werden an den Text angehängt | MP4, MOV; bis 1 GB; 1 Sek. bis 20 Min. | |
| Veröffentlicht nur auf Seiten; auf persönliche Profile lässt sich über die API nicht posten. | |||||
| Eigene API | Text | TitelOhne Längenbegrenzung | BeschreibungOhne Längenbegrenzung | Werden als Tags gesendet | Keine |
| Bild | TitelOhne Längenbegrenzung | BeschreibungOhne Längenbegrenzung | Werden als Tags gesendet | JPEG, PNG, WebP, GIF | |
| Video | TitelOhne Längenbegrenzung | BeschreibungOhne Längenbegrenzung | Werden als Tags gesendet | MP4, MOV, WebM | |
| Jede Anfrage trägt eine HMAC-SHA256-Signatur im Header, damit du prüfen kannst, dass sie von Amaposta kommt. Antworte mit 2xx und optional mit JSON {"id": "…", "url": "…"}. Bei 408, 429 und 5xx wird mit zunehmendem Abstand erneut versucht; 401/403 markieren den Kanal zum Neuverbinden; andere 4xx lassen die Zustellung fehlschlagen, damit du sie korrigieren und erneut versuchen kannst. | |||||
| Sandbox | Text | TitelBis zu 200 Zeichen | BeschreibungBis zu 5.000 Zeichen | Werden an den Text angehängt, bis zu 30 | Keine |
| Bild | TitelBis zu 200 Zeichen | BeschreibungBis zu 5.000 Zeichen | Werden an den Text angehängt, bis zu 30 | JPEG, PNG, WebP, GIF | |
| Video | TitelBis zu 200 Zeichen | BeschreibungBis zu 5.000 Zeichen | Werden an den Text angehängt, bis zu 30 | MP4, MOV, WebM | |
| Das Veröffentlichen dauert einen Moment, damit du den Statuswechsel live verfolgen kannst. | |||||