Panoramica
Panoramica dell'API
Ogni endpoint di Sofia API v1 con parametri, scope ed esempi, e un playground per provarli uno a uno con la tua chiave API.
URL di base https://api.sofia-post.com/api · Documento OpenAPI
Sofia API dà al tuo codice accesso al tuo account Sofia: le attività dei tuoi Workspace, le loro reti collegate, la loro libreria media, i loro post e le loro statistiche. È un’API REST su HTTPS; richieste e risposte sono in JSON. Permette di:
- Pubblicare: subito, a una data o come bozza, su una o più reti alla volta.
- Verificare prima di pubblicare: una simulazione ti dice se ogni rete accetterebbe il post, senza scrivere nulla.
- Gestire i media: carica un file o importalo da un URL nella libreria media di un’attività.
- Seguire i post: leggi lo stato di ogni rete, il suo permalink o il motivo del fallimento. Un webhook firmato può anche avvisarti.
- Leggere le statistiche: gli ultimi numeri raccolti da Sofia per ogni account collegato.
- Collegare le reti: crea un link ospitato che permette al titolare di collegare una rete mancante.
SDK e integrazioni
- CLI `sofia`: la CLI, pubblicata su npm con il nome
sofia-post, per controllare Sofia da un terminale. - Server MCP: collega Sofia ai tuoi agenti IA.
- Zapier: l’integrazione Sofia si collega con la tua chiave personale. Offre i trigger «Post Published» e «Post Failed», l’azione «Create Post» (pubblicare, programmare o salvare una bozza) e la ricerca «Find Post». Non è ancora stata sottoposta alla directory pubblica di Zapier.
Autenticazione
Ogni chiamata porta un token Bearer nell’header Authorization. Sofia rilegge i diritti collegati a ogni chiamata: una persona rimossa da un Workspace, o un’autorizzazione revocata, ferma il token dalla richiesta successiva.
Ci sono due modi per autenticarti:
| Metodo | Per | Token |
|---|---|---|
| Chiave personale | Automatizzare il tuo account, script, agenti | sofia_pk_live_…, valida fino alla scadenza |
| OAuth, authorization code + PKCE | Un prodotto che agisce per i suoi utenti, sul Workspace che scelgono | sofia_live_at_…, un’ora, rinnovabile |
| OAuth, client credentials | Un tuo server che automatizza un Workspace di cui sei titolare | sofia_live_at_…, un’ora |
Chiave API
Se automatizzi il tuo Workspace, basta una chiave personale. Nell’app Sofia apri Impostazioni › API, scheda Accesso personale, e crea una chiave. Scegli il suo nome, la sua scadenza e i suoi permessi. La chiave viene mostrata una sola volta: copiala e conservala in un vault di segreti, mai nel tuo codice.
Crea una chiave in Impostazioni › API › Accesso personale. Agisce per te su tutti i tuoi Workspace: l’attività che indichi determina il Workspace, e il tuo ruolo al suo interno decide cosa la chiave può fare.
Invia la chiave nell’header Authorization, come token Bearer:
curl https://api.sofia-post.com/api/v1/establishments \
-H "Authorization: Bearer sofia_pk_live_…"`GET /v1/me` ti indica per chi agisce la chiave, i suoi Workspace e i suoi permessi.
Una chiamata senza il permesso (lo scope) richiesto risponde 403 insufficient_scope.
Token OAuth2
Per un prodotto SaaS che agisce per i suoi utenti sui loro Workspace, registra un client OAuth: ogni persona autorizza la tua applicazione sul Workspace che sceglie. I token di accesso OAuth iniziano con sofia_live_at_…, durano un’ora e si usano allo stesso modo:
curl https://api.sofia-post.com/api/v1/establishments \
-H "Authorization: Bearer sofia_live_at_…"Crei un’app a cui si collegano altri account Sofia? Il suo flusso authorization code con PKCE, le sue client credentials e i suoi scope sono descritti in OAuth2.
URL di base
Ogni endpoint si trova sotto un unico URL di base:
https://api.sofia-post.com/api/v1Limiti di frequenza
Sofia API conta le richieste su tre finestre: 15 minuti, 24 ore e 30 giorni.
| Token | 15 minuti | 24 ore | 30 giorni |
|---|---|---|---|
| Chiave personale (per account) | 150 | 1.000 | 10.000 |
| Client OAuth | 100 | 500 | 15.000 |
| Client OAuth, per persona autorizzata (fino a 1.000) | +10 | +25 | +100 |
Header
Ogni risposta ti dice cosa resta, nel formato 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=2592000r è il numero di richieste rimanenti, t i secondi prima del reset della finestra, q la quota e w la finestra in secondi.
Quando una finestra è piena
La risposta è 429 rate_limit_exceeded, con un header Retry-After e la finestra esaurita in details. Aspetta questo intervallo prima di riprovare.
Usare l’API in modo efficiente
- Registra un webhook invece di interrogare un post in continuazione.
- Usa
limitfino a 100 per leggere le liste con meno pagine. - Metti in cache ciò che cambia raramente: piattaforme, attività, account collegati.
Errori
Ogni errore risponde con uno stato HTTP e la stessa busta JSON. code è il contratto che legge il tuo codice; message è scritto per una persona e può cambiare.
{
"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"
}
}detailsdice di più: il campo rifiutato, il motivo di una rete, la finestra di limite esaurita.docUrlporta alla spiegazione del codice in questa pagina.requestIdidentifica la richiesta: comunicalo all’assistenza.
Codici di errore
| Stato | Codice | Significato |
|---|---|---|
| 400 | invalid_request | Il body, la query o un parametro non è valido. Un dettaglio per campo rifiutato: field, code (required, is_string, max…) e message. |
| 400 | invalid_json | Il body non è un JSON valido. |
| 400 | invalid_cursor | Il cursore non è stato restituito da Sofia API. |
| 400 | webhook_url_invalid | L'indirizzo del webhook non è https, o porta a un indirizzo privato. |
| 400 | webhook_event_invalid | Evento webhook sconosciuto. |
| 400 | application_name_invalid | Impostazioni › API: il nome del client manca o è troppo lungo. |
| 400 | application_profile_invalid | Impostazioni › API: un campo del profilo del client non è valido; il dettaglio lo indica. |
| 400 | logo_invalid | Impostazioni › API: il logo è rifiutato (almeno 180×180 px, PNG, JPEG o WebP, massimo 2 MB). |
| 401 | invalid_token | Il token manca, è sconosciuto, scaduto o revocato, oppure la persona dietro di esso ha perso i diritti che porta. Vedi l'header WWW-Authenticate. |
| 401 | unauthorized | È richiesta l'autenticazione. |
| 402 | subscription_required | Una scrittura reale (simulazione compresa) richiede un abbonamento attivo o una prova. |
| 403 | insufficient_scope | Al token manca lo scope dell'endpoint; il dettaglio lo indica. |
| 403 | insufficient_role | Il tuo ruolo in questo Workspace non consente questa azione. |
| 403 | live_mode_required | Una scrittura reale con un token di test. |
| 403 | route_closed_to_applications | Questo endpoint non è aperto alle applicazioni. |
| 403 | forbidden | L'accesso è rifiutato. |
| 403 | session_required | Impostazioni › API: questa azione richiede una sessione Sofia, non un token. |
| 403 | impersonation_forbidden | Impostazioni › API: questa azione è rifiutata in una sessione presa in prestito. |
| 403 | workspace_owner_required | Solo il proprietario del Workspace può farlo. |
| 403 | workspace_member_required | Non sei membro di questo Workspace. |
| 404 | route_not_found | Nessun endpoint risponde a questo metodo e a questo percorso. |
| 404 | establishment_not_found | Attività sconosciuta, o fuori dalla portata del token. |
| 404 | media_not_found | Media sconosciuto, o fuori dalla portata del token. |
| 404 | post_not_found | Pubblicazione sconosciuta, o fuori dalla portata del token. |
| 404 | webhook_not_found | Webhook sconosciuto, o di un'altra applicazione. |
| 404 | not_found | La risorsa non esiste. |
| 404 | application_not_found | Impostazioni › API: client sconosciuto. |
| 404 | grant_not_found | Autorizzazione sconosciuta o già revocata. |
| 404 | personal_key_not_found | Impostazioni › API: chiave personale sconosciuta. |
| 404 | consent_not_found | La richiesta di autorizzazione è scaduta o non esiste: riparti dall'endpoint di autorizzazione. |
| 405 | method_not_allowed | Questo metodo non è consentito su questo endpoint. |
| 409 | post_in_flight | Una rete sta inviando la pubblicazione: riprova l'eliminazione un momento dopo. |
| 409 | post_not_editable | Una rete ha già inviato la pubblicazione: non può più essere modificata. |
| 409 | account_already_connected | Questa rete è già collegata all'attività. |
| 409 | webhook_limit_reached | L'applicazione ha già 10 webhook. |
| 409 | idempotency_in_progress | La prima richiesta con questa Idempotency-Key è ancora in corso. |
| 409 | conflict | La richiesta è in conflitto con lo stato attuale della risorsa. |
| 409 | scope_removal_forbidden | Impostazioni › API: un permesso di un client può essere aggiunto ma non rimosso. |
| 409 | public_client_has_no_secret | Impostazioni › API: un client pubblico non ha un secret. |
| 413 | payload_too_large | Il file supera il limite di caricamento. |
| 415 | unsupported_media_type | Tipo di contenuto non supportato. |
| 422 | post_refused | Una rete, l'attività o il tuo piano rifiuterebbe la pubblicazione. Un dettaglio per motivo, ognuno con il suo message. |
| 422 | media_import_refused | Non è stato possibile importare il media da questo URL; il dettaglio dà il motivo. |
| 422 | media_upload_refused | Il file caricato non è un'immagine o un video supportato. |
| 422 | platform_not_offered | Questa rete non è offerta per il collegamento. |
| 422 | idempotency_key_reused | Questa Idempotency-Key è stata usata con un body diverso. |
| 422 | unprocessable_entity | La richiesta è comprensibile ma non può essere elaborata. |
| 429 | rate_limit_exceeded | Una finestra di limite è piena: attendi Retry-After. |
Motivi di un post rifiutato
Un post_refused elenca ogni motivo in details[].code:
| Codice | Significato |
|---|---|
unsupported_platform | Questa rete non è supportata. |
duplicate_platform | La stessa rete è indicata due volte. |
unsupported_format | Questa rete non offre questo formato. |
caption_too_long | Il testo è troppo lungo (max, actual). |
too_many_hashtags | Troppi hashtag (max, actual). |
too_many_mentions | Troppe menzioni (max, actual). |
too_many_thread_posts | Il thread è troppo lungo (max, actual). |
too_many_media | Troppi media (max, actual). |
media_mix_refused | Foto e video non si possono mescolare su questa rete. |
media_required | Questa rete richiede una foto o un video. |
media_type_refused | Questa rete non accetta questo tipo di media. |
scheduled_in_past | scheduledAt è nel passato. |
scheduled_at_invalid | Una pubblicazione programmata richiede uno scheduledAt futuro. |
account_not_connected | Nessun account collegato su questa rete per questa attività. |
account_ambiguous | Su questa rete sono collegati più account: indicane uno con accountId. |
media_not_found | Il media non è nella mediateca di questa attività. |
media_url_invalid | L'URL del media deve essere un indirizzo https pubblico. |
platform_unavailable | Questa rete non è ancora aperta su Sofia. |
publication_quota_exceeded | La quota di pubblicazioni del tuo piano è raggiunta. |
Gestire gli errori
- Basa la tua logica su
code, mai sumessage. - Riprova un
429dopoRetry-After, e un5xxcon un backoff esponenziale. - Non riprovare mai un
4xxidentico: correggi prima la richiesta. - Registra
requestIda ogni fallimento.
Paginazione
Le liste (GET /v1/posts, GET /v1/media) arrivano dalle più recenti, una pagina alla volta. limit fissa la dimensione della pagina, da 1 a 100 (20 per impostazione predefinita).
{
"data": [ … ],
"nextCursor": "WyIyMDI2LTEwLTI3VDE1OjExOjU5LjAxN1oiLCIx…"
}Passa nextCursor come cursor per leggere la pagina successiva. Quando nextCursor è assente, sei arrivato alla fine.
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 cursore è opaco: non costruirlo né modificarlo mai. Un cursore che Sofia non ha restituito risponde 400 invalid_cursor.
Idempotenza
Un errore di rete può lasciarti nel dubbio sulla creazione di un post. Invia un header Idempotency-Key con POST /v1/posts e POST /v1/media, e riprova con la stessa chiave: Sofia risponde ciò che ha risposto la prima volta e non crea duplicati.
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- Usa una nuova chiave casuale (un UUID) per ogni nuovo post, e la stessa chiave per ogni nuovo tentativo di quel post.
- La stessa chiave con un corpo diverso risponde
422 idempotency_key_reused. - Finché la prima richiesta è in corso, la stessa chiave risponde
409 idempotency_in_progress: riprova poco dopo.
Modalità test e simulazione
Simulazione. Aggiungi ?dry_run=true a POST /v1/posts: Sofia esegue i controlli di un vero post (le regole di ogni rete, l’account collegato, i media, la data e il tuo piano) senza salvare né inviare nulla. Il dettaglio delle risposte è nella pagina Pubblicare.
Modalità test. Un’applicazione creata in modalità test (token sofia_test_at_…) legge il tuo vero Workspace ma non vi scrive mai: i suoi post sono simulati e valutati con le stesse regole, e ogni scrittura reale risponde 403 live_mode_required. Le chiavi e i client creati in Impostazioni › API sono in modalità live; usa la simulazione per verificare senza pubblicare.
OAuth
/v1/oauth/authorizeAvviare un'autorizzazioneAprila nel browser. Un client sconosciuto o una redirect_uri non registrata dà un 400; altrimenti reindirizza alla schermata di consenso di Sofia.GET/.well-known/oauth-authorization-server/api/v1/oauthLeggere i metadati del serverRestituisce i metadati del server di autorizzazione OAuth 2.0 di Sofia API (RFC 8414), così un client scopre gli endpoint e le opzioni supportate.POST/v1/oauth/tokenEmettere un token di accessoScambia client_credentials, un authorization_code con PKCE o un refresh_token monouso con un token opaco di 3600 s per un Workspace.Account
Piattaforme
Attività
/v1/establishmentsElencare le attivitàElenca le attività del Workspace del token. Usa i loro identificatori come establishmentId per pubblicare, elencare i media o leggere le statistiche.GET/v1/establishments/{id}/accountsElencare gli account collegatiElenca le reti collegate a un'attività, senza token né secret di rete, solo ciò che serve per nominare un account e sceglierlo come accountId.POST/v1/establishments/{id}/connection-linksCreare un link di collegamentoRestituisce un link ospitato dove la persona autorizza una rete con Sofia, senza che alcun token di rete arrivi alla tua app. Valido 30 minuti; solo app live.GET/v1/establishments/{id}/insightsLeggere le statisticheRestituisce gli ultimi dati validi raccolti da Sofia per ogni account collegato. Un account non ancora raccolto mostra awaiting, mai zero.Media
/v1/mediaElencare i mediaElenca la mediateca di un'attività, i più recenti prima, pagina per pagina, con URL firmati per la finestra corrente. Funziona in test e in live.POST/v1/mediaImportare o caricare un mediaAggiunge alla mediateca di un'attività un file (multipart) o un URL https che Sofia scarica. Richiede un'app live e un abbonamento attivo.GET/v1/media/{id}Leggere un mediaRestituisce un media della mediateca con un URL firmato valido per la finestra corrente, così puoi riutilizzarne l'identificatore in una pubblicazione.DELETE/v1/media/{id}Rimuovere un mediaRimuove un media dalla mediateca della sua attività, come fa l'app. Il file non viene distrutto e una pubblicazione programmata mantiene il suo visual.Pubblicazioni
/v1/postsElencare le pubblicazioniElenca le pubblicazioni delle attività raggiungibili dal token, le più recenti prima, filtrabili per attività, stato, date e autore, pagina per pagina.POST/v1/postsPubblicare una pubblicazioneLe app live pubblicano subito, programmano o salvano una bozza; quelle di test simulano soltanto. Con ?dry_run=true controlla tutto e non scrive nulla.GET/v1/posts/{id}Leggere una pubblicazioneRestituisce una pubblicazione con lo stato di ogni rete, il link una volta pubblicata e l'errore. Live: ogni pubblicazione del Workspace; test: una simulata.PATCH/v1/posts/{id}Modificare una pubblicazioneFinché nessuna rete ha iniziato l'invio, sostituisce targets, imposta scheduledAt o passa tra draft e scheduled. I campi omessi restano invariati.DELETE/v1/posts/{id}Eliminare una pubblicazioneElimina una pubblicazione come fa l'app: nulla di programmato viene inviato e una già pubblicata è rimossa dalla sua rete. In test non c'è nulla da eliminare.Webhook
/v1/webhooksElencare i webhookElenca i webhook dell'applicazione senza il loro secret di firma, che viene restituito solo quando il webhook è registrato.POST/v1/webhooksRegistrare un webhookRegistra un URL https che Sofia chiama con un evento JSON firmato, ritentato fino a cinque volte se fallisce. Secret mostrato una volta; massimo 10 webhook.DELETE/v1/webhooks/{id}Eliminare un webhookElimina un webhook. Non riceve più nulla, nemmeno le consegne che attendevano un nuovo tentativo, e la chiamata non restituisce alcun body.POST/v1/webhooks/{id}/testInviare un evento di testConsegna un evento webhook.test firmato come gli altri, senza attendere il ricevente, così puoi verificare la tua firma. Al massimo cinque al minuto.