ÜberblickAPI-Übersicht

Ü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-post verö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:

MethodeWofürToken
Persönlicher SchlüsselDein eigenes Konto automatisieren, Skripte, Agentssofia_pk_live_…, gültig bis zum Ablauf
OAuth, Authorization Code + PKCEEin Produkt, das für seine Nutzer im Workspace ihrer Wahl handeltsofia_live_at_…, eine Stunde, erneuerbar
OAuth, Client CredentialsEiner deiner Server, der einen Workspace automatisiert, dessen Inhaber du bistsofia_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/v1

Rate Limits

Sofia API zählt die Anfragen in drei Zeitfenstern: 15 Minuten, 24 Stunden und 30 Tage.

Token15 Minuten24 Stunden30 Tage
Persönlicher Schlüssel (pro Konto)1501.00010.000
OAuth-Client10050015.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=2592000

r 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 limit bis 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"
  }
}
  • details sagt mehr: das abgelehnte Feld, den Grund eines Netzwerks, das erschöpfte Limit-Zeitfenster.
  • docUrl führt zur Erklärung des Codes auf dieser Seite.
  • requestId identifiziert die Anfrage: Gib sie dem Support weiter.

Fehlercodes

StatusCodeBedeutung
400invalid_requestDer Body, die Query oder ein Parameter ist ungültig. Ein Detail pro abgelehntem Feld: field, code (required, is_string, max …) und message.
400invalid_jsonDer Body ist kein gültiges JSON.
400invalid_cursorDer Cursor wurde nicht von Sofia API zurückgegeben.
400webhook_url_invalidDie Webhook-Adresse ist nicht https oder führt zu einer privaten Adresse.
400webhook_event_invalidUnbekanntes Webhook-Event.
400application_name_invalidEinstellungen › API: Der Name des Clients fehlt oder ist zu lang.
400application_profile_invalidEinstellungen › API: Ein Feld des Client-Profils ist ungültig; das Detail nennt es.
400logo_invalidEinstellungen › API: Das Logo wird abgelehnt (mindestens 180×180 px, PNG, JPEG oder WebP, höchstens 2 MB).
401invalid_tokenDas Token fehlt, ist unbekannt, abgelaufen oder widerrufen, oder die Person dahinter hat die Rechte verloren, die es trägt. Siehe den Header WWW-Authenticate.
401unauthorizedAuthentifizierung erforderlich.
402subscription_requiredEin echter Schreibzugriff (Probelauf eingeschlossen) erfordert ein aktives Abonnement oder eine Testphase.
403insufficient_scopeDem Token fehlt der Scope des Endpoints; das Detail nennt ihn.
403insufficient_roleIhre Rolle in diesem Workspace erlaubt diese Aktion nicht.
403live_mode_requiredEin echter Schreibzugriff mit einem Test-Token.
403route_closed_to_applicationsDieser Endpoint steht Anwendungen nicht offen.
403forbiddenZugriff verweigert.
403session_requiredEinstellungen › API: Diese Aktion braucht eine Sofia-Sitzung, kein Token.
403impersonation_forbiddenEinstellungen › API: Diese Aktion wird in einer geliehenen Sitzung abgelehnt.
403workspace_owner_requiredNur der Eigentümer des Workspace kann das tun.
403workspace_member_requiredSie sind kein Mitglied dieses Workspace.
404route_not_foundKein Endpoint antwortet auf diese Methode und diesen Pfad.
404establishment_not_foundUnbekannter Betrieb oder außerhalb der Reichweite des Tokens.
404media_not_foundUnbekanntes Medium oder außerhalb der Reichweite des Tokens.
404post_not_foundUnbekannter Beitrag oder außerhalb der Reichweite des Tokens.
404webhook_not_foundUnbekannter Webhook oder der einer anderen Anwendung.
404not_foundDie Ressource existiert nicht.
404application_not_foundEinstellungen › API: Unbekannter Client.
404grant_not_foundUnbekannte oder bereits widerrufene Autorisierung.
404personal_key_not_foundEinstellungen › API: Unbekannter persönlicher Schlüssel.
405method_not_allowedDiese Methode ist auf diesem Endpunkt nicht erlaubt.
409post_in_flightEin Netzwerk sendet den Beitrag gerade: Versuchen Sie das Löschen einen Moment später erneut.
409post_not_editableEin Netzwerk hat den Beitrag bereits gesendet: Er kann nicht mehr bearbeitet werden.
409account_already_connectedDieses Netzwerk ist bereits mit dem Betrieb verbunden.
409webhook_limit_reachedDie Anwendung hat bereits 10 Webhooks.
409idempotency_in_progressDie erste Anfrage mit diesem Idempotency-Key läuft noch.
409conflictDie Anfrage steht im Widerspruch zum aktuellen Zustand der Ressource.
409scope_removal_forbiddenEinstellungen › API: Eine Berechtigung eines Clients kann hinzugefügt, aber nicht entfernt werden.
409public_client_has_no_secretEinstellungen › API: Ein öffentlicher Client hat kein Secret.
413payload_too_largeDie Datei überschreitet das Upload-Limit.
415unsupported_media_typeNicht unterstützter Content-Type.
422post_refusedEin Netzwerk, der Betrieb oder Ihr Tarif würde den Beitrag ablehnen. Ein Detail pro Grund, jeweils mit seiner message.
422media_import_refusedDas Medium konnte nicht von dieser URL importiert werden; das Detail nennt den Grund.
422media_upload_refusedDie hochgeladene Datei ist kein unterstütztes Bild oder Video.
422platform_not_offeredDieses Netzwerk wird nicht zur Verbindung angeboten.
422idempotency_key_reusedDieser Idempotency-Key wurde mit einem anderen Body verwendet.
422unprocessable_entityDie Anfrage wird verstanden, kann aber nicht verarbeitet werden.
429rate_limit_exceededEin 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:

CodeBedeutung
unsupported_platformDieses Netzwerk wird nicht unterstützt.
duplicate_platformDasselbe Netzwerk wird zweimal genannt.
unsupported_formatDieses Netzwerk bietet dieses Format nicht an.
caption_too_longDer Text ist zu lang (max, actual).
too_many_hashtagsZu viele Hashtags (max, actual).
too_many_mentionsZu viele Erwähnungen (max, actual).
too_many_thread_postsDer Thread ist zu lang (max, actual).
too_many_mediaZu viele Medien (max, actual).
media_mix_refusedFotos und Videos lassen sich in diesem Netzwerk nicht mischen.
media_requiredDieses Netzwerk braucht ein Foto oder ein Video.
media_type_refusedDieses Netzwerk akzeptiert diesen Medientyp nicht.
scheduled_in_pastscheduledAt liegt in der Vergangenheit.
scheduled_at_invalidEin geplanter Beitrag braucht ein zukünftiges scheduledAt.
account_not_connectedFür diesen Betrieb ist in diesem Netzwerk kein Konto verbunden.
account_ambiguousIn diesem Netzwerk sind mehrere Konten verbunden: Geben Sie eines mit accountId an.
media_not_foundDas Medium befindet sich nicht in der Mediathek dieses Betriebs.
media_url_invalidDie Medien-URL muss eine öffentliche https-Adresse sein.
platform_unavailableDieses Netzwerk ist bei Sofia noch nicht freigeschaltet.
publication_quota_exceededDas Veröffentlichungskontingent Ihres Tarifs ist erreicht.

Fehler behandeln

  • Baue deine Logik auf code auf, niemals auf message.
  • Wiederhole ein 429 nach Retry-After und ein 5xx mit exponentiellem Backoff.
  • Wiederhole ein 4xx niemals unverändert: Korrigiere zuerst die Anfrage.
  • Protokolliere requestId bei 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

Konto

Plattformen

Betriebe

Medien

Beiträge

Webhooks