Resumen
Descripción general de la API
Cada endpoint de Sofia API v1 con sus parámetros, scopes y ejemplos, y un playground para probar cada uno con tu propia clave de API.
URL base https://api.sofia-post.com/api · Documento OpenAPI
Sofia API da a tu código acceso a tu cuenta de Sofia: los establecimientos de tus Workspaces, sus redes conectadas, su biblioteca multimedia, sus publicaciones y sus estadísticas. Es una API REST sobre HTTPS; las solicitudes y las respuestas son JSON. Permite:
- Publicar: ahora, en una fecha o como borrador, en una o varias redes a la vez.
- Comprobar antes de publicar: una simulación te dice si cada red aceptaría la publicación, sin escribir nada.
- Gestionar los archivos: sube un archivo o impórtalo desde una URL a la biblioteca multimedia de un establecimiento.
- Seguir las publicaciones: lee el estado de cada red, su enlace permanente o el motivo del error. Un webhook firmado también puede avisarte.
- Leer las estadísticas: las últimas cifras recopiladas por Sofia para cada cuenta conectada.
- Conectar redes: crea un enlace alojado que permite al propietario conectar una red que falta.
SDK e integraciones
- CLI `sofia`: la CLI, publicada en npm con el nombre
sofia-post, para manejar Sofia desde un terminal. - Servidor MCP: conecta Sofia a tus agentes de IA.
- Zapier: la integración de Sofia se conecta con tu clave personal. Ofrece los disparadores «Post Published» y «Post Failed», la acción «Create Post» (publicar, programar o guardar un borrador) y la búsqueda «Find Post». Todavía no se ha enviado al directorio público de Zapier.
Autenticación
Cada llamada lleva un token Bearer en la cabecera Authorization. Sofia vuelve a leer los derechos asociados en cada llamada: una persona retirada de un Workspace, o una autorización revocada, detiene el token desde la solicitud siguiente.
Hay dos formas de autenticarse:
| Método | Para | Token |
|---|---|---|
| Clave personal | Automatizar tu propia cuenta, scripts, agentes | sofia_pk_live_…, válido hasta su caducidad |
| OAuth, código de autorización + PKCE | Un producto que actúa por sus usuarios, en el Workspace que elijan | sofia_live_at_…, una hora, renovable |
| OAuth, client credentials | Uno de tus servidores que automatiza un Workspace del que eres propietario | sofia_live_at_…, una hora |
Clave de API
Si automatizas tu propio Workspace, basta una clave personal. En la app de Sofia, abre Ajustes › API, pestaña Acceso personal, y crea una clave. Elige su nombre, su caducidad y sus permisos. La clave solo se muestra una vez: cópiala y guárdala en una caja fuerte de secretos, nunca en tu código.
Crea una clave en Ajustes › API › Acceso personal. Actúa por ti en todos tus Workspaces: el establecimiento que nombras determina el Workspace, y tu rol en él decide lo que la clave puede hacer.
Envía la clave en la cabecera Authorization, como token Bearer:
curl https://api.sofia-post.com/api/v1/establishments \
-H "Authorization: Bearer sofia_pk_live_…"`GET /v1/me` te indica por quién actúa la clave, sus Workspaces y sus permisos.
Una llamada sin el permiso (el scope) necesario responde 403 insufficient_scope.
Token OAuth2
Para un producto SaaS que actúa por sus usuarios en sus propios Workspaces, registra un cliente OAuth: cada persona autoriza tu aplicación en el Workspace que elija. Los tokens de acceso OAuth empiezan por sofia_live_at_…, duran una hora y se usan del mismo modo:
curl https://api.sofia-post.com/api/v1/establishments \
-H "Authorization: Bearer sofia_live_at_…"¿Creas una app a la que se conectan otras cuentas de Sofia? Su flujo authorization code con PKCE, sus client credentials y sus scopes se describen en OAuth2.
URL base
Cada endpoint se encuentra bajo una única URL base:
https://api.sofia-post.com/api/v1Límites de frecuencia
Sofia API cuenta las solicitudes en tres ventanas: 15 minutos, 24 horas y 30 días.
| Token | 15 minutos | 24 horas | 30 días |
|---|---|---|---|
| Clave personal (por cuenta) | 150 | 1.000 | 10.000 |
| Cliente OAuth | 100 | 500 | 15.000 |
| Cliente OAuth, por persona autorizada (hasta 1.000) | +10 | +25 | +100 |
Cabeceras
Cada respuesta te dice lo que queda, en 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 es el número de solicitudes restantes, t los segundos antes de que se reinicie la ventana, q la cuota y w la ventana en segundos.
Cuando una ventana está llena
La respuesta es 429 rate_limit_exceeded, con una cabecera Retry-After y la ventana agotada en details. Espera ese plazo antes de reintentar.
Usar la API con eficacia
- Registra un webhook en lugar de consultar una publicación en bucle.
- Usa
limithasta 100 para leer las listas en menos páginas. - Guarda en caché lo que cambia poco: plataformas, establecimientos, cuentas conectadas.
Errores
Cada error responde con un estado HTTP y el mismo sobre JSON. code es el contrato que lee tu código; message está escrito para una persona y puede cambiar.
{
"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 más: el campo rechazado, el motivo de una red, la ventana de límite agotada.docUrllleva a la explicación del código en esta página.requestIdidentifica la solicitud: comunícaselo al soporte.
Códigos de error
| Estado | Código | Significado |
|---|---|---|
| 400 | invalid_request | El cuerpo, la consulta o un parámetro no es válido. Un detalle por campo rechazado: field, code (required, is_string, max…) y message. |
| 400 | invalid_json | El cuerpo no es JSON válido. |
| 400 | invalid_cursor | El cursor no lo devolvió Sofia API. |
| 400 | webhook_url_invalid | La dirección del webhook no es https, o lleva a una dirección privada. |
| 400 | webhook_event_invalid | Evento de webhook desconocido. |
| 400 | application_name_invalid | Ajustes › API: falta el nombre del cliente o es demasiado largo. |
| 400 | application_profile_invalid | Ajustes › API: un campo del perfil del cliente no es válido; el detalle lo indica. |
| 400 | logo_invalid | Ajustes › API: el logotipo se rechaza (mínimo 180×180 px, PNG, JPEG o WebP, 2 MB como máximo). |
| 401 | invalid_token | El token falta, es desconocido, ha caducado o está revocado, o la persona que hay detrás perdió los derechos que lleva. Consulta la cabecera WWW-Authenticate. |
| 401 | unauthorized | Se requiere autenticación. |
| 402 | subscription_required | Una escritura real (simulación incluida) exige una suscripción activa o una prueba. |
| 403 | insufficient_scope | Al token le falta el scope del endpoint; el detalle lo nombra. |
| 403 | insufficient_role | Tu rol en este Workspace no permite esta acción. |
| 403 | live_mode_required | Una escritura real con un token de prueba. |
| 403 | route_closed_to_applications | Este endpoint no está abierto a aplicaciones. |
| 403 | forbidden | Acceso denegado. |
| 403 | session_required | Ajustes › API: esta acción requiere una sesión de Sofia, no un token. |
| 403 | impersonation_forbidden | Ajustes › API: esta acción se rechaza en una sesión prestada. |
| 403 | workspace_owner_required | Solo el propietario del Workspace puede hacerlo. |
| 403 | workspace_member_required | No eres miembro de este Workspace. |
| 404 | route_not_found | Ningún endpoint responde a este método y esta ruta. |
| 404 | establishment_not_found | Establecimiento desconocido, o fuera del alcance del token. |
| 404 | media_not_found | Medio desconocido, o fuera del alcance del token. |
| 404 | post_not_found | Publicación desconocida, o fuera del alcance del token. |
| 404 | webhook_not_found | Webhook desconocido, o de otra aplicación. |
| 404 | not_found | El recurso no existe. |
| 404 | application_not_found | Ajustes › API: cliente desconocido. |
| 404 | grant_not_found | Autorización desconocida o ya revocada. |
| 404 | personal_key_not_found | Ajustes › API: clave personal desconocida. |
| 404 | consent_not_found | La solicitud de autorización ha caducado o no existe: empieza de nuevo desde el endpoint de autorización. |
| 405 | method_not_allowed | Este método no está permitido en este endpoint. |
| 409 | post_in_flight | Una red está enviando la publicación: reintenta la eliminación un momento después. |
| 409 | post_not_editable | Una red ya envió la publicación: ya no se puede editar. |
| 409 | account_already_connected | Esta red ya está conectada al establecimiento. |
| 409 | webhook_limit_reached | La aplicación ya tiene 10 webhooks. |
| 409 | idempotency_in_progress | La primera petición con esta Idempotency-Key sigue en curso. |
| 409 | conflict | La solicitud entra en conflicto con el estado actual del recurso. |
| 409 | scope_removal_forbidden | Ajustes › API: a un cliente se le puede añadir un permiso, pero no quitárselo. |
| 409 | public_client_has_no_secret | Ajustes › API: un cliente público no tiene secreto. |
| 413 | payload_too_large | El archivo supera el límite de subida. |
| 415 | unsupported_media_type | Tipo de contenido no admitido. |
| 422 | post_refused | Una red, el establecimiento o tu plan rechazaría la publicación. Un detalle por motivo, cada uno con su message. |
| 422 | media_import_refused | No se pudo importar el medio desde esta URL; el detalle da el motivo. |
| 422 | media_upload_refused | El archivo subido no es una imagen o un vídeo admitido. |
| 422 | platform_not_offered | Esta red no se ofrece para conectar. |
| 422 | idempotency_key_reused | Esta Idempotency-Key se usó con un cuerpo distinto. |
| 422 | unprocessable_entity | La solicitud se entiende, pero no se puede procesar. |
| 429 | rate_limit_exceeded | Una ventana de límite está llena: espera el tiempo de Retry-After. |
Motivos de una publicación rechazada
Un post_refused lista cada motivo en details[].code:
| Código | Significado |
|---|---|
unsupported_platform | Esta red no es compatible. |
duplicate_platform | La misma red aparece dos veces. |
unsupported_format | Esta red no ofrece este formato. |
caption_too_long | El texto es demasiado largo (max, actual). |
too_many_hashtags | Demasiados hashtags (max, actual). |
too_many_mentions | Demasiadas menciones (max, actual). |
too_many_thread_posts | El hilo es demasiado largo (max, actual). |
too_many_media | Demasiados medios (max, actual). |
media_mix_refused | No se pueden mezclar fotos y vídeos en esta red. |
media_required | Esta red necesita una foto o un vídeo. |
media_type_refused | Esta red no acepta este tipo de medio. |
scheduled_in_past | scheduledAt está en el pasado. |
scheduled_at_invalid | Una publicación programada necesita un scheduledAt futuro. |
account_not_connected | No hay ninguna cuenta conectada en esta red para este establecimiento. |
account_ambiguous | Hay varias cuentas conectadas en esta red: indica una con accountId. |
media_not_found | El medio no está en la biblioteca de este establecimiento. |
media_url_invalid | La URL del medio debe ser una dirección https pública. |
platform_unavailable | Esta red todavía no está abierta en Sofia. |
publication_quota_exceeded | Se alcanzó la cuota de publicaciones de tu plan. |
Gestionar los errores
- Basa tu lógica en
code, nunca enmessage. - Reintenta un
429después deRetry-After, y un5xxcon un backoff exponencial. - No reintentes nunca un
4xxidéntico: corrige primero la solicitud. - Registra
requestIden cada fallo.
Paginación
Las listas (GET /v1/posts, GET /v1/media) se devuelven con las más recientes primero, una página cada vez. limit fija el tamaño de página, de 1 a 100 (20 por defecto).
{
"data": [ … ],
"nextCursor": "WyIyMDI2LTEwLTI3VDE1OjExOjU5LjAxN1oiLCIx…"
}Pasa nextCursor como cursor para leer la página siguiente. Cuando nextCursor no aparece, has llegado al final.
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 cursor es opaco: nunca lo construyas ni lo modifiques. Un cursor que Sofia no ha devuelto responde 400 invalid_cursor.
Idempotencia
Un error de red puede dejarte con la duda sobre la creación de una publicación. Envía una cabecera Idempotency-Key con POST /v1/posts y POST /v1/media, y reintenta con la misma clave: Sofia responde lo que respondió la primera vez, y no crea nada por duplicado.
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 clave aleatoria nueva (un UUID) para cada publicación nueva, y la misma clave para cada nuevo intento de esa publicación.
- La misma clave con un cuerpo distinto responde
422 idempotency_key_reused. - Mientras la primera solicitud está en curso, la misma clave responde
409 idempotency_in_progress: reintenta un momento después.
Modo de prueba y simulación
Simulación. Añade ?dry_run=true a POST /v1/posts: Sofia ejecuta las comprobaciones de una publicación real (las reglas de cada red, la cuenta conectada, los archivos, la fecha y tu plan) sin guardar ni enviar nada. El detalle de las respuestas está en la página Publicar.
Modo de prueba. Una aplicación creada en modo test (tokens sofia_test_at_…) lee tu Workspace real pero nunca escribe en él: sus publicaciones se simulan y se juzgan con las mismas reglas, y toda escritura real responde 403 live_mode_required. Las claves y los clientes creados en Ajustes › API están en modo live; usa la simulación para comprobar sin publicar.
OAuth
/v1/oauth/authorizeIniciar una autorizaciónÁbrela en el navegador. Un cliente desconocido o una redirect_uri no registrada da un 400; si no, redirige a la pantalla de consentimiento de Sofia.GET/.well-known/oauth-authorization-server/api/v1/oauthObtener metadatos del servidorDevuelve los metadatos del servidor de autorización OAuth 2.0 de Sofia API (RFC 8414), para que un cliente descubra los endpoints y las opciones admitidas.POST/v1/oauth/tokenEmitir un token de accesoCanjea client_credentials, un authorization_code con PKCE o un refresh_token de un solo uso por un token opaco de 3600 s para un Workspace.Cuenta
Plataformas
Establecimientos
/v1/establishmentsListar los establecimientosLista los establecimientos del Workspace del token. Usa sus identificadores como establishmentId para publicar, listar medios o leer estadísticas.GET/v1/establishments/{id}/accountsListar las cuentas conectadasLista las redes conectadas a un establecimiento, sin token ni secreto de red, solo lo necesario para nombrar una cuenta y elegirla como accountId.POST/v1/establishments/{id}/connection-linksCrear un enlace de conexiónDevuelve un enlace alojado donde la persona autoriza una red con Sofia, sin que ningún token de red llegue a tu app. Válido 30 minutos; solo apps live.GET/v1/establishments/{id}/insightsObtener estadísticasDevuelve las últimas cifras válidas que Sofia recopiló de cada cuenta conectada. Una cuenta aún no recopilada muestra awaiting, nunca cero.Medios
/v1/mediaListar los mediosLista la biblioteca de medios de un establecimiento, los más recientes primero, por páginas, con URL firmadas para la ventana actual. Vale en prueba y en live.POST/v1/mediaImportar o subir un medioAñade a la biblioteca de medios de un establecimiento un archivo (multipart) o una URL https que Sofia descarga. Requiere una app live y suscripción activa.GET/v1/media/{id}Obtener un medioDevuelve un medio de la biblioteca con una URL firmada válida para la ventana actual, para que puedas reutilizar su identificador en una publicación.DELETE/v1/media/{id}Retirar un medioRetira un medio de la biblioteca de su establecimiento, como lo hace la app. El archivo no se destruye y una publicación programada conserva su imagen.Publicaciones
/v1/postsListar las publicacionesLista las publicaciones de los establecimientos accesibles al token, las más recientes primero, filtrables por establecimiento, estado, fechas y autor.POST/v1/postsPublicar una publicaciónLas apps live publican al instante, programan o guardan un borrador; las de prueba solo simulan. Con ?dry_run=true se comprueba todo y no se escribe nada.GET/v1/posts/{id}Obtener una publicaciónDevuelve una publicación con el estado de cada red, su enlace una vez publicada y su error. En live, cualquiera del Workspace; en prueba, una simulada.PATCH/v1/posts/{id}Editar una publicaciónMientras ninguna red haya empezado a enviar, reemplaza targets, fija scheduledAt o pasa entre draft y scheduled. Los campos omitidos se conservan.DELETE/v1/posts/{id}Eliminar una publicaciónElimina una publicación como la app: no se envía nada programado y una ya publicada se retira de su red. En prueba no hay nada real que eliminar.Webhooks
/v1/webhooksListar los webhooksLista los webhooks de la aplicación sin su secreto de firma, que solo se devuelve cuando se registra el webhook.POST/v1/webhooksRegistrar un webhookRegistra una URL https a la que Sofia llama con un evento JSON firmado, reintentado hasta cinco veces si falla. Secreto visible una vez; máximo 10 webhooks.DELETE/v1/webhooks/{id}Eliminar un webhookElimina un webhook. Ya no recibe nada, ni siquiera los envíos que esperaban un reintento, y la llamada no devuelve cuerpo.POST/v1/webhooks/{id}/testEnviar un evento de pruebaEntrega un evento webhook.test firmado como cualquier otro, sin esperar al receptor, para que compruebes tu firma. Como máximo cinco por minuto.