Vue d’ensembleAperçu de l'API

Vue d’ensemble

Aperçu de l'API

Chaque endpoint de Sofia API v1 avec ses paramètres, ses scopes et ses exemples, et un playground pour tester chacun avec votre propre clé d’API.

URL de base https://api.sofia-post.com/api · Document OpenAPI

Sofia API donne à votre code l’accès à votre compte Sofia : les établissements de vos Workspaces, leurs réseaux connectés, leur médiathèque, leurs publications et leurs statistiques. C’est une API REST en HTTPS ; les requêtes et les réponses sont en JSON. Elle permet de :

  • Publier : maintenant, à une date ou en brouillon, sur un ou plusieurs réseaux à la fois.
  • Vérifier avant de publier : une simulation vous dit si chaque réseau accepterait la publication, sans rien écrire.
  • Gérer les médias : téléversez un fichier ou importez-le depuis une URL dans la médiathèque d’un établissement.
  • Suivre les publications : lisez l’état de chaque réseau, son lien permanent ou la raison de l’échec. Un webhook signé peut aussi vous prévenir.
  • Lire les statistiques : les derniers chiffres collectés par Sofia pour chaque compte connecté.
  • Connecter des réseaux : créez un lien hébergé qui permet au propriétaire de connecter un réseau manquant.

SDK et intégrations

  • CLI `sofia` : la CLI, publiée sur npm sous le nom sofia-post, pour piloter Sofia depuis un terminal.
  • Serveur MCP : branchez Sofia à vos agents IA.
  • Zapier : l’intégration Sofia se connecte avec votre clé personnelle. Elle offre les déclencheurs « Post Published » et « Post Failed », l’action « Create Post » (publier, programmer ou enregistrer un brouillon) et la recherche « Find Post ». Elle n’est pas encore soumise à l’annuaire public de Zapier.

Authentification

Chaque appel porte un jeton Bearer dans l’en-tête Authorization. Sofia relit les droits qui s’y rattachent à chaque appel : une personne retirée d’un Workspace, ou une autorisation révoquée, arrête le jeton dès la requête suivante.

Il y a deux façons de vous authentifier :

MéthodePourJeton
Clé personnelleAutomatiser votre propre compte, scripts, agentssofia_pk_live_…, valide jusqu’à son expiration
OAuth, code d’autorisation + PKCEUn produit qui agit pour ses utilisateurs, sur le Workspace de leur choixsofia_live_at_…, une heure, renouvelable
OAuth, client credentialsUn de vos serveurs qui automatise un Workspace dont vous êtes propriétairesofia_live_at_…, une heure

Clé API

Si vous automatisez votre propre Workspace, une clé personnelle suffit. Dans l’app Sofia, ouvrez Réglages › API, onglet Accès personnel, et créez une clé. Choisissez son nom, son expiration et ses permissions. La clé n’est affichée qu’une fois : copiez-la et gardez-la dans un coffre de secrets, jamais dans votre code.

Créez une clé dans Réglages › API › Accès personnel. Elle agit pour vous sur tous vos Workspaces : l’établissement que vous nommez détermine le Workspace, et votre rôle dans celui-ci décide de ce que la clé peut faire.

Envoyez la clé dans l’en-tête Authorization, comme jeton Bearer :

curl https://api.sofia-post.com/api/v1/establishments \
  -H "Authorization: Bearer sofia_pk_live_…"

`GET /v1/me` vous indique pour qui la clé agit, ses Workspaces et ses permissions.

Un appel sans la permission (le scope) requise répond 403 insufficient_scope.

Jeton OAuth2

Pour un produit SaaS qui agit pour ses utilisateurs sur leurs propres Workspaces, enregistrez un client OAuth : chaque personne autorise votre application sur le Workspace de son choix. Les jetons d’accès OAuth commencent par sofia_live_at_…, durent une heure et s’utilisent de la même façon :

curl https://api.sofia-post.com/api/v1/establishments \
  -H "Authorization: Bearer sofia_live_at_…"

Vous créez une app à laquelle d’autres comptes Sofia se connectent ? Son flux authorization code avec PKCE, ses client credentials et ses scopes sont décrits dans OAuth2.

URL de base

Chaque endpoint se trouve sous une seule URL de base :

https://api.sofia-post.com/api/v1

Limites de débit

Sofia API compte les requêtes sur trois fenêtres : 15 minutes, 24 heures et 30 jours.

Jeton15 minutes24 heures30 jours
Clé personnelle (par compte)1501 00010 000
Client OAuth10050015 000
Client OAuth, par personne autorisée (jusqu’à 1 000)+10+25+100

En-têtes

Chaque réponse vous dit ce qu’il reste, au format IETF :

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 est le nombre de requêtes restantes, t les secondes avant la réinitialisation de la fenêtre, q le quota et w la fenêtre en secondes.

Quand une fenêtre est pleine

La réponse est 429 rate_limit_exceeded, avec un en-tête Retry-After et la fenêtre épuisée dans details. Attendez ce délai avant de réessayer.

Utiliser l’API efficacement

  • Enregistrez un webhook plutôt que d’interroger une publication en boucle.
  • Utilisez limit jusqu’à 100 pour lire les listes en moins de pages.
  • Mettez en cache ce qui change rarement : plateformes, établissements, comptes connectés.

Erreurs

Chaque erreur répond avec un statut HTTP et la même enveloppe JSON. code est le contrat que lit votre code ; message est écrit pour un humain et peut changer.

{
  "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 en dit plus : le champ refusé, la raison d’un réseau, la fenêtre de limite épuisée.
  • docUrl mène à l’explication du code sur cette page.
  • requestId identifie la requête : communiquez-le au support.

Codes d’erreur

StatutCodeSignification
400invalid_requestLe corps, la requête ou un paramètre est invalide. Un détail par champ refusé : field, code (required, is_string, max…) et message.
400invalid_jsonLe corps n’est pas du JSON valide.
400invalid_cursorLe curseur n’a pas été renvoyé par Sofia API.
400webhook_url_invalidL’adresse du webhook n’est pas en https, ou mène à une adresse privée.
400webhook_event_invalidÉvénement de webhook inconnu.
400application_name_invalidRéglages › API : le nom du client est absent ou trop long.
400application_profile_invalidRéglages › API : un champ du profil du client est invalide ; le détail le nomme.
400logo_invalidRéglages › API : le logo est refusé (au moins 180×180 px, PNG, JPEG ou WebP, 2 Mo au plus).
401invalid_tokenLe jeton est absent, inconnu, expiré ou révoqué, ou la personne derrière a perdu les droits qu’il porte. Voir l’en-tête WWW-Authenticate.
401unauthorizedUne authentification est requise.
402subscription_requiredUne écriture réelle (simulation comprise) exige un abonnement actif ou un essai.
403insufficient_scopeLe jeton n’a pas le scope de l’endpoint ; le détail le nomme.
403insufficient_roleVotre rôle dans ce Workspace n’autorise pas cette action.
403live_mode_requiredUne écriture réelle avec un jeton de test.
403route_closed_to_applicationsCet endpoint n’est pas ouvert aux applications.
403forbiddenL’accès est refusé.
403session_requiredRéglages › API : cette action demande une session Sofia, pas un jeton.
403impersonation_forbiddenRéglages › API : cette action est refusée dans une session empruntée.
403workspace_owner_requiredSeul le propriétaire du Workspace peut faire cela.
403workspace_member_requiredVous n’êtes pas membre de ce Workspace.
404route_not_foundAucun endpoint ne répond à cette méthode et ce chemin.
404establishment_not_foundÉtablissement inconnu, ou hors de portée du jeton.
404media_not_foundMédia inconnu, ou hors de portée du jeton.
404post_not_foundPublication inconnue, ou hors de portée du jeton.
404webhook_not_foundWebhook inconnu, ou appartenant à une autre application.
404not_foundLa ressource n’existe pas.
404application_not_foundRéglages › API : client inconnu.
404grant_not_foundAutorisation inconnue, ou déjà révoquée.
404personal_key_not_foundRéglages › API : clé personnelle inconnue.
405method_not_allowedCette méthode n’est pas autorisée sur cet endpoint.
409post_in_flightUn réseau est en train d’envoyer la publication : réessayez la suppression un instant plus tard.
409post_not_editableUn réseau a déjà envoyé la publication : elle ne peut plus être modifiée.
409account_already_connectedCe réseau est déjà connecté à l’établissement.
409webhook_limit_reachedL’application a déjà 10 webhooks.
409idempotency_in_progressLa première requête avec cette Idempotency-Key est encore en cours.
409conflictLa requête entre en conflit avec l’état actuel de la ressource.
409scope_removal_forbiddenRéglages › API : une permission d’un client peut être ajoutée mais pas retirée.
409public_client_has_no_secretRéglages › API : un client public n’a pas de secret.
413payload_too_largeLe fichier dépasse la limite de téléversement.
415unsupported_media_typeType de contenu non pris en charge.
422post_refusedUn réseau, l’établissement ou votre forfait refuserait la publication. Un détail par raison, chacun avec son message.
422media_import_refusedLe média n’a pas pu être importé depuis cette URL ; le détail donne la raison.
422media_upload_refusedLe fichier téléversé n’est pas une image ou une vidéo prise en charge.
422platform_not_offeredCe réseau n’est pas proposé à la connexion.
422idempotency_key_reusedCette Idempotency-Key a servi avec un corps différent.
422unprocessable_entityLa requête est comprise mais ne peut pas être traitée.
429rate_limit_exceededUne fenêtre de limite est pleine : attendez Retry-After.

Raisons d’une publication refusée

Un post_refused liste chaque raison dans details[].code :

CodeSignification
unsupported_platformCe réseau n’est pas pris en charge.
duplicate_platformLe même réseau est nommé deux fois.
unsupported_formatCe réseau ne propose pas ce format.
caption_too_longLe texte est trop long (max, actual).
too_many_hashtagsTrop de hashtags (max, actual).
too_many_mentionsTrop de mentions (max, actual).
too_many_thread_postsLe fil est trop long (max, actual).
too_many_mediaTrop de médias (max, actual).
media_mix_refusedPhotos et vidéos ne peuvent pas être mélangées sur ce réseau.
media_requiredCe réseau exige une photo ou une vidéo.
media_type_refusedCe réseau n’accepte pas ce type de média.
scheduled_in_pastscheduledAt est dans le passé.
scheduled_at_invalidUne publication programmée exige un scheduledAt futur.
account_not_connectedAucun compte connecté sur ce réseau pour cet établissement.
account_ambiguousPlusieurs comptes sont connectés sur ce réseau : nommez-en un avec accountId.
media_not_foundLe média n’est pas dans la médiathèque de cet établissement.
media_url_invalidL’URL du média doit être une adresse https publique.
platform_unavailableCe réseau n’est pas encore ouvert sur Sofia.
publication_quota_exceededLe quota de publications de votre forfait est atteint.

Gérer les erreurs

  • Branchez votre logique sur code, jamais sur message.
  • Réessayez un 429 après Retry-After, et un 5xx avec un backoff exponentiel.
  • Ne réessayez jamais un 4xx à l’identique : corrigez d’abord la requête.
  • Journalisez requestId à chaque échec.

Pagination

Les listes (GET /v1/posts, GET /v1/media) sont renvoyées les plus récentes d’abord, une page à la fois. limit fixe la taille de page, de 1 à 100 (20 par défaut).

{
  "data": [ … ],
  "nextCursor": "WyIyMDI2LTEwLTI3VDE1OjExOjU5LjAxN1oiLCIx…"
}

Passez nextCursor comme cursor pour lire la page suivante. Quand nextCursor est absent, vous avez atteint la fin.

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);

Un curseur est opaque : ne le construisez ni ne le modifiez jamais. Un curseur que Sofia n’a pas renvoyé répond 400 invalid_cursor.

Idempotence

Une erreur réseau peut vous laisser dans le doute sur la création d’une publication. Envoyez un en-tête Idempotency-Key avec POST /v1/posts et POST /v1/media, et réessayez avec la même clé : Sofia répond ce qu’elle a répondu la première fois, et ne crée rien en double.

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
  • Utilisez une nouvelle clé aléatoire (un UUID) pour chaque nouvelle publication, et la même clé pour chaque nouvel essai de cette publication.
  • La même clé avec un corps différent répond 422 idempotency_key_reused.
  • Tant que la première requête est en cours, la même clé répond 409 idempotency_in_progress : réessayez un instant plus tard.

Mode test et simulation

Simulation. Ajoutez ?dry_run=true à POST /v1/posts : Sofia exécute les contrôles d’une vraie publication (les règles de chaque réseau, le compte connecté, les médias, la date et votre forfait) sans rien enregistrer ni envoyer. Le détail des réponses est sur la page Publier.

Mode test. Une application créée en mode test (jetons sofia_test_at_…) lit votre vrai Workspace mais n’y écrit jamais : ses publications sont simulées et jugées selon les mêmes règles, et toute écriture réelle répond 403 live_mode_required. Les clés et clients créés dans Réglages › API sont en mode live ; utilisez la simulation pour vérifier sans publier.

OAuth

Compte

Plateformes

Établissements

Médias

Publications

Webhooks