Überblick
API-Übersicht
Jeder Endpoint von Sofia API v1 mit Parametern, Scopes und Beispielen sowie ein Playground, um jeden mit Ihrem eigenen API-Schlüssel auszuprobieren.
Basis-URL https://api.sofia-post.com/api · OpenAPI-Dokument
Sofia API gibt deinem Code Zugriff auf dein Sofia-Konto: die Betriebe deiner Workspaces, ihre verbundenen Netzwerke, ihre Mediathek, ihre Beiträge und ihre Statistiken. Es ist eine REST-API über HTTPS; Anfragen und Antworten sind JSON. Damit kannst du:
- Veröffentlichen: sofort, zu einem Datum oder als Entwurf, auf einem oder mehreren Netzwerken gleichzeitig.
- Vor dem Veröffentlichen prüfen: Eine Simulation sagt dir, ob jedes Netzwerk den Beitrag akzeptieren würde, ohne etwas zu schreiben.
- Medien verwalten: Lade eine Datei hoch oder importiere sie per URL in die Mediathek eines Betriebs.
- Beiträge verfolgen: Lies den Status jedes Netzwerks, seinen Permalink oder den Grund des Fehlschlags. Ein signierter Webhook kann dich auch benachrichtigen.
- Statistiken lesen: die letzten Zahlen, die Sofia für jedes verbundene Konto erfasst hat.
- Netzwerke verbinden: Erstelle einen gehosteten Link, über den der Inhaber ein fehlendes Netzwerk verbinden kann.
SDKs und Integrationen
- CLI `sofia`: die CLI, auf npm unter dem Namen
sofia-postveröffentlicht, um Sofia vom Terminal aus zu steuern. - MCP-Server: Verbinde Sofia mit deinen KI-Agenten.
- Zapier: Die Sofia-Integration verbindest du mit deinem persönlichen Schlüssel. Sie bietet die Trigger „Post Published“ und „Post Failed“, die Aktion „Create Post“ (veröffentlichen, planen oder einen Entwurf speichern) und die Suche „Find Post“. Sie ist noch nicht im öffentlichen Zapier-Verzeichnis eingereicht.
Authentifizierung
Jeder Aufruf trägt ein Bearer-Token im Header Authorization. Sofia liest die damit verbundenen Rechte bei jedem Aufruf neu: Wird eine Person aus einem Workspace entfernt oder eine Autorisierung widerrufen, ist das Token ab der nächsten Anfrage ungültig.
Du kannst dich auf zwei Arten authentifizieren:
| Methode | Wofür | Token |
|---|---|---|
| Persönlicher Schlüssel | Dein eigenes Konto automatisieren, Skripte, Agents | sofia_pk_live_…, gültig bis zum Ablauf |
| OAuth, Authorization Code + PKCE | Ein Produkt, das für seine Nutzer im Workspace ihrer Wahl handelt | sofia_live_at_…, eine Stunde, erneuerbar |
| OAuth, Client Credentials | Einer deiner Server, der einen Workspace automatisiert, dessen Inhaber du bist | sofia_live_at_…, eine Stunde |
API-Schlüssel
Wenn du deinen eigenen Workspace automatisierst, genügt ein persönlicher Schlüssel. Öffne in der Sofia-App Einstellungen › API, Tab Persönlicher Zugriff, und erstelle einen Schlüssel. Wähle seinen Namen, sein Ablaufdatum und seine Berechtigungen. Der Schlüssel wird nur einmal angezeigt: Kopiere ihn und bewahre ihn in einem Secret-Tresor auf, niemals in deinem Code.
Erstelle einen Schlüssel unter Einstellungen › API › Persönlicher Zugriff. Er handelt für dich in all deinen Workspaces: Der Betrieb, den du nennst, bestimmt den Workspace, und deine Rolle darin entscheidet, was der Schlüssel darf.
Sende den Schlüssel im Header Authorization als Bearer-Token:
curl https://api.sofia-post.com/api/v1/establishments \
-H "Authorization: Bearer sofia_pk_live_…"`GET /v1/me` zeigt dir, für wen der Schlüssel handelt, seine Workspaces und seine Berechtigungen.
Ein Aufruf ohne die erforderliche Berechtigung (den Scope) antwortet mit 403 insufficient_scope.
OAuth2-Token
Für ein SaaS-Produkt, das für seine Nutzer in deren eigenen Workspaces handelt, registrierst du einen OAuth-Client: Jede Person autorisiert deine Anwendung für den Workspace ihrer Wahl. OAuth-Zugriffstoken beginnen mit sofia_live_at_…, gelten eine Stunde und werden genauso verwendet:
curl https://api.sofia-post.com/api/v1/establishments \
-H "Authorization: Bearer sofia_live_at_…"Du erstellst eine App, mit der sich andere Sofia-Konten verbinden? Ihr Authorization-Code-Flow mit PKCE, ihre Client Credentials und ihre Scopes sind unter OAuth2 beschrieben.
Basis-URL
Jeder Endpoint liegt unter einer einzigen Basis-URL:
https://api.sofia-post.com/api/v1Rate Limits
Sofia API zählt die Anfragen in drei Zeitfenstern: 15 Minuten, 24 Stunden und 30 Tage.
| Token | 15 Minuten | 24 Stunden | 30 Tage |
|---|---|---|---|
| Persönlicher Schlüssel (pro Konto) | 150 | 1.000 | 10.000 |
| OAuth-Client | 100 | 500 | 15.000 |
| OAuth-Client, pro autorisierter Person (bis zu 1.000) | +10 | +25 | +100 |
Header
Jede Antwort sagt dir, was übrig ist, im IETF-Format:
RateLimit: "15m";r=144;t=43, "24h";r=994;t=13543, "30d";r=9994;t=13543
RateLimit-Policy: "15m";q=150;w=900, "24h";q=1000;w=86400, "30d";q=10000;w=2592000r ist die Zahl der verbleibenden Anfragen, t die Sekunden bis zum Zurücksetzen des Zeitfensters, q das Kontingent und w das Zeitfenster in Sekunden.
Wenn ein Zeitfenster voll ist
Die Antwort ist 429 rate_limit_exceeded, mit einem Header Retry-After und dem erschöpften Zeitfenster in details. Warte diese Zeit ab, bevor du es erneut versuchst.
Die API effizient nutzen
- Registriere einen Webhook, statt einen Beitrag in einer Schleife abzufragen.
- Nutze
limitbis 100, um Listen mit weniger Seiten zu lesen. - Cache, was sich selten ändert: Plattformen, Betriebe, verbundene Konten.
Fehler
Jeder Fehler antwortet mit einem HTTP-Status und demselben JSON-Umschlag. code ist der Vertrag, den dein Code liest; message ist für Menschen geschrieben und kann sich ändern.
{
"error": {
"code": "invalid_request",
"message": "The request is invalid",
"details": [
{ "field": "targets[0].text", "code": "required", "message": "targets[0].text is required" }
],
"docUrl": "https://www.sofia-post.com/developers/errors#invalid_request",
"requestId": "2aab5943-6333-4caf-8478-25a85ebd4abf"
}
}detailssagt mehr: das abgelehnte Feld, den Grund eines Netzwerks, das erschöpfte Limit-Zeitfenster.docUrlführt zur Erklärung des Codes auf dieser Seite.requestIdidentifiziert die Anfrage: Gib sie dem Support weiter.
Fehlercodes
| Status | Code | Bedeutung |
|---|---|---|
| 400 | invalid_request | Der Body, die Query oder ein Parameter ist ungültig. Ein Detail pro abgelehntem Feld: field, code (required, is_string, max …) und message. |
| 400 | invalid_json | Der Body ist kein gültiges JSON. |
| 400 | invalid_cursor | Der Cursor wurde nicht von Sofia API zurückgegeben. |
| 400 | webhook_url_invalid | Die Webhook-Adresse ist nicht https oder führt zu einer privaten Adresse. |
| 400 | webhook_event_invalid | Unbekanntes Webhook-Event. |
| 400 | application_name_invalid | Einstellungen › API: Der Name des Clients fehlt oder ist zu lang. |
| 400 | application_profile_invalid | Einstellungen › API: Ein Feld des Client-Profils ist ungültig; das Detail nennt es. |
| 400 | logo_invalid | Einstellungen › API: Das Logo wird abgelehnt (mindestens 180×180 px, PNG, JPEG oder WebP, höchstens 2 MB). |
| 401 | invalid_token | Das Token fehlt, ist unbekannt, abgelaufen oder widerrufen, oder die Person dahinter hat die Rechte verloren, die es trägt. Siehe den Header WWW-Authenticate. |
| 401 | unauthorized | Authentifizierung erforderlich. |
| 402 | subscription_required | Ein echter Schreibzugriff (Probelauf eingeschlossen) erfordert ein aktives Abonnement oder eine Testphase. |
| 403 | insufficient_scope | Dem Token fehlt der Scope des Endpoints; das Detail nennt ihn. |
| 403 | insufficient_role | Ihre Rolle in diesem Workspace erlaubt diese Aktion nicht. |
| 403 | live_mode_required | Ein echter Schreibzugriff mit einem Test-Token. |
| 403 | route_closed_to_applications | Dieser Endpoint steht Anwendungen nicht offen. |
| 403 | forbidden | Zugriff verweigert. |
| 403 | session_required | Einstellungen › API: Diese Aktion braucht eine Sofia-Sitzung, kein Token. |
| 403 | impersonation_forbidden | Einstellungen › API: Diese Aktion wird in einer geliehenen Sitzung abgelehnt. |
| 403 | workspace_owner_required | Nur der Eigentümer des Workspace kann das tun. |
| 403 | workspace_member_required | Sie sind kein Mitglied dieses Workspace. |
| 404 | route_not_found | Kein Endpoint antwortet auf diese Methode und diesen Pfad. |
| 404 | establishment_not_found | Unbekannter Betrieb oder außerhalb der Reichweite des Tokens. |
| 404 | media_not_found | Unbekanntes Medium oder außerhalb der Reichweite des Tokens. |
| 404 | post_not_found | Unbekannter Beitrag oder außerhalb der Reichweite des Tokens. |
| 404 | webhook_not_found | Unbekannter Webhook oder der einer anderen Anwendung. |
| 404 | not_found | Die Ressource existiert nicht. |
| 404 | application_not_found | Einstellungen › API: Unbekannter Client. |
| 404 | grant_not_found | Unbekannte oder bereits widerrufene Autorisierung. |
| 404 | personal_key_not_found | Einstellungen › API: Unbekannter persönlicher Schlüssel. |
| 404 | consent_not_found | Die Autorisierungsanfrage ist abgelaufen oder existiert nicht: Beginnen Sie erneut beim Autorisierungs-Endpunkt. |
| 405 | method_not_allowed | Diese Methode ist auf diesem Endpunkt nicht erlaubt. |
| 409 | post_in_flight | Ein Netzwerk sendet den Beitrag gerade: Versuchen Sie das Löschen einen Moment später erneut. |
| 409 | post_not_editable | Ein Netzwerk hat den Beitrag bereits gesendet: Er kann nicht mehr bearbeitet werden. |
| 409 | account_already_connected | Dieses Netzwerk ist bereits mit dem Betrieb verbunden. |
| 409 | webhook_limit_reached | Die Anwendung hat bereits 10 Webhooks. |
| 409 | idempotency_in_progress | Die erste Anfrage mit diesem Idempotency-Key läuft noch. |
| 409 | conflict | Die Anfrage steht im Widerspruch zum aktuellen Zustand der Ressource. |
| 409 | scope_removal_forbidden | Einstellungen › API: Eine Berechtigung eines Clients kann hinzugefügt, aber nicht entfernt werden. |
| 409 | public_client_has_no_secret | Einstellungen › API: Ein öffentlicher Client hat kein Secret. |
| 413 | payload_too_large | Die Datei überschreitet das Upload-Limit. |
| 415 | unsupported_media_type | Nicht unterstützter Content-Type. |
| 422 | post_refused | Ein Netzwerk, der Betrieb oder Ihr Tarif würde den Beitrag ablehnen. Ein Detail pro Grund, jeweils mit seiner message. |
| 422 | media_import_refused | Das Medium konnte nicht von dieser URL importiert werden; das Detail nennt den Grund. |
| 422 | media_upload_refused | Die hochgeladene Datei ist kein unterstütztes Bild oder Video. |
| 422 | platform_not_offered | Dieses Netzwerk wird nicht zur Verbindung angeboten. |
| 422 | idempotency_key_reused | Dieser Idempotency-Key wurde mit einem anderen Body verwendet. |
| 422 | unprocessable_entity | Die Anfrage wird verstanden, kann aber nicht verarbeitet werden. |
| 429 | rate_limit_exceeded | Ein Rate-Limit-Fenster ist voll: Warten Sie Retry-After ab. |
Gründe für einen abgelehnten Beitrag
Ein post_refused listet jeden Grund in details[].code auf:
| Code | Bedeutung |
|---|---|
unsupported_platform | Dieses Netzwerk wird nicht unterstützt. |
duplicate_platform | Dasselbe Netzwerk wird zweimal genannt. |
unsupported_format | Dieses Netzwerk bietet dieses Format nicht an. |
caption_too_long | Der Text ist zu lang (max, actual). |
too_many_hashtags | Zu viele Hashtags (max, actual). |
too_many_mentions | Zu viele Erwähnungen (max, actual). |
too_many_thread_posts | Der Thread ist zu lang (max, actual). |
too_many_media | Zu viele Medien (max, actual). |
media_mix_refused | Fotos und Videos lassen sich in diesem Netzwerk nicht mischen. |
media_required | Dieses Netzwerk braucht ein Foto oder ein Video. |
media_type_refused | Dieses Netzwerk akzeptiert diesen Medientyp nicht. |
scheduled_in_past | scheduledAt liegt in der Vergangenheit. |
scheduled_at_invalid | Ein geplanter Beitrag braucht ein zukünftiges scheduledAt. |
account_not_connected | Für diesen Betrieb ist in diesem Netzwerk kein Konto verbunden. |
account_ambiguous | In diesem Netzwerk sind mehrere Konten verbunden: Geben Sie eines mit accountId an. |
media_not_found | Das Medium befindet sich nicht in der Mediathek dieses Betriebs. |
media_url_invalid | Die Medien-URL muss eine öffentliche https-Adresse sein. |
platform_unavailable | Dieses Netzwerk ist bei Sofia noch nicht freigeschaltet. |
publication_quota_exceeded | Das Veröffentlichungskontingent Ihres Tarifs ist erreicht. |
Fehler behandeln
- Baue deine Logik auf
codeauf, niemals aufmessage. - Wiederhole ein
429nachRetry-Afterund ein5xxmit exponentiellem Backoff. - Wiederhole ein
4xxniemals unverändert: Korrigiere zuerst die Anfrage. - Protokolliere
requestIdbei jedem Fehlschlag.
Paginierung
Listen (GET /v1/posts, GET /v1/media) kommen mit den neuesten Einträgen zuerst, eine Seite nach der anderen. limit legt die Seitengröße fest, von 1 bis 100 (Standard 20).
{
"data": [ … ],
"nextCursor": "WyIyMDI2LTEwLTI3VDE1OjExOjU5LjAxN1oiLCIx…"
}Übergib nextCursor als cursor, um die nächste Seite zu lesen. Fehlt nextCursor, bist du am Ende angekommen.
let cursor;
do {
const url = new URL('https://api.sofia-post.com/api/v1/posts');
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('cursor', cursor);
const page = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${key}` } }).then((r) => r.json());
handle(page.data);
cursor = page.nextCursor;
} while (cursor);Ein Cursor ist undurchsichtig: Baue ihn niemals selbst und ändere ihn nie. Ein Cursor, den Sofia nicht geliefert hat, antwortet mit 400 invalid_cursor.
Idempotenz
Ein Netzwerkfehler kann dich im Zweifel lassen, ob ein Beitrag erstellt wurde. Sende einen Header Idempotency-Key mit POST /v1/posts und POST /v1/media und wiederhole mit demselben Schlüssel: Sofia antwortet, was sie beim ersten Mal geantwortet hat, und legt nichts doppelt an.
curl -X POST https://api.sofia-post.com/api/v1/posts \
-H "Authorization: Bearer $SOFIA_API_KEY" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d @post.json- Verwende für jeden neuen Beitrag einen neuen zufälligen Schlüssel (eine UUID) und für jeden weiteren Versuch dieses Beitrags denselben Schlüssel.
- Derselbe Schlüssel mit einem anderen Body antwortet mit
422 idempotency_key_reused. - Solange die erste Anfrage läuft, antwortet derselbe Schlüssel mit
409 idempotency_in_progress: Versuche es einen Moment später erneut.
Testmodus und Simulation
Simulation. Füge ?dry_run=true zu POST /v1/posts hinzu: Sofia führt die Prüfungen eines echten Beitrags aus (die Regeln jedes Netzwerks, das verbundene Konto, die Medien, das Datum und deinen Tarif), ohne etwas zu speichern oder zu senden. Die Details der Antworten stehen auf der Seite Veröffentlichen.
Testmodus. Eine im Testmodus erstellte Anwendung (Token sofia_test_at_…) liest deinen echten Workspace, schreibt aber nie hinein: Ihre Beiträge werden simuliert und nach denselben Regeln beurteilt, und jeder echte Schreibzugriff antwortet mit 403 live_mode_required. Die in Einstellungen › API erstellten Schlüssel und Clients sind im Live-Modus; nutze die Simulation, um zu prüfen, ohne zu veröffentlichen.
OAuth
/v1/oauth/authorizeEine Autorisierung startenIm Browser der Person öffnen. Unbekannter Client oder nicht registrierte redirect_uri ergibt 400, sonst folgt die Weiterleitung zum Zustimmungsbildschirm.GET/.well-known/oauth-authorization-server/api/v1/oauthServer-Metadaten abrufenLiefert die OAuth-2.0-Metadaten des Autorisierungsservers der Sofia API (RFC 8414), damit ein Client die Endpoints und die unterstützten Optionen findet.POST/v1/oauth/tokenEin Access-Token ausstellenTauscht client_credentials, einen authorization_code mit PKCE oder ein einmaliges refresh_token gegen ein opakes Token (3600 s) für einen Workspace.Konto
Plattformen
Betriebe
/v1/establishmentsBetriebe auflistenListet die Betriebe des Workspace des Tokens auf. Nutzen Sie ihre Kennungen als establishmentId zum Veröffentlichen, für Medien und Statistiken.GET/v1/establishments/{id}/accountsVerbundene Konten auflistenListet die mit einem Betrieb verbundenen Netzwerke auf, ohne Token oder Netzwerk-Secret, nur das Nötige, um ein Konto zu benennen und als accountId zu wählen.POST/v1/establishments/{id}/connection-linksEinen Verbindungslink erstellenLiefert einen gehosteten Link, über den die Person ein Netzwerk bei Sofia autorisiert, ohne dass ein Netzwerk-Token Ihre App erreicht. 30 Min. gültig, nur live.GET/v1/establishments/{id}/insightsStatistiken eines Betriebs abrufenLiefert die jüngsten gültigen Zahlen, die Sofia für jedes verbundene Konto gesammelt hat. Ein noch nicht erfasstes Konto zeigt awaiting, nie null.Medien
/v1/mediaMedien auflistenListet die Mediathek eines Betriebs auf, neueste zuerst, seitenweise, mit URLs, die für das aktuelle Zeitfenster signiert sind. Funktioniert im Test und live.POST/v1/mediaMedium importieren oder hochladenFügt der Mediathek eines Betriebs eine Datei (Multipart) oder eine https-URL hinzu, die Sofia abruft. Erfordert eine Live-App und ein aktives Abonnement.GET/v1/media/{id}Ein Medium abrufenLiefert ein Medium der Mediathek mit einer für das aktuelle Zeitfenster gültigen signierten URL, damit Sie seine Kennung in einem Beitrag nutzen können.DELETE/v1/media/{id}Ein Medium entfernenEntfernt ein Medium aus der Mediathek seines Betriebs, wie es die App tut. Die Datei wird nicht zerstört, ein geplanter Beitrag behält sein Bild.Beiträge
/v1/postsBeiträge auflistenListet die Beiträge der für das Token erreichbaren Betriebe auf, neueste zuerst, filterbar nach Betrieb, Status, Zeitraum und Autor, seitenweise.POST/v1/postsEinen Beitrag veröffentlichenLive-Apps veröffentlichen sofort, planen oder speichern einen Entwurf; Test-Apps simulieren nur. Mit ?dry_run=true wird alles geprüft und nichts geschrieben.GET/v1/posts/{id}Einen Beitrag abrufenLiefert einen Beitrag mit dem Zustand jedes Netzwerks, Link nach der Veröffentlichung und Fehler. Live jeden Beitrag des Workspace, im Test einen simulierten.PATCH/v1/posts/{id}Einen Beitrag bearbeitenSolange kein Netzwerk zu senden begann, ersetzt es targets, setzt scheduledAt oder wechselt zwischen draft und scheduled. Fehlende Felder bleiben.DELETE/v1/posts/{id}Einen Beitrag löschenLöscht einen Beitrag wie die App: Nichts Geplantes wird gesendet, ein veröffentlichter Beitrag verschwindet aus seinem Netzwerk. Im Test ist nichts zu löschen.Webhooks
/v1/webhooksWebhooks auflistenListet die Webhooks der Anwendung ohne ihr Signatur-Secret auf, das nur bei der Registrierung des Webhooks zurückgegeben wird.POST/v1/webhooksEinen Webhook registrierenRegistriert eine https-URL, die Sofia mit einem signierten JSON-Ereignis aufruft, bei Fehlern bis zu fünfmal wiederholt. Secret einmal sichtbar; max. 10.DELETE/v1/webhooks/{id}Einen Webhook löschenLöscht einen Webhook. Er erhält nichts mehr, auch nicht die Zustellungen, die noch auf einen neuen Versuch warteten, und der Aufruf liefert keinen Body.POST/v1/webhooks/{id}/testEin Testereignis sendenStellt ein signiertes webhook.test-Ereignis wie jedes andere zu, ohne auf den Empfänger zu warten, damit Sie Ihre Signatur prüfen. Höchstens fünf pro Minute.