Guía rápida de la API REST de Shager: de una clave nueva a una publicación con seguimiento en 40 cuentas, en siete llamadas
Recorrido práctico por la API REST de Shager en curl y TypeScript: autenticación, espacios, canales, archivos, borradores por cuenta, programación y clics.

La API REST de Shager está en https://app.shager.ai/api/v1, se autentica con Authorization: Bearer shgr_live_..., devuelve cada resultado como { "data": ... } y cada error como { "error": { "code", "message" } }. Una tanda de distribución completa son siete llamadas: verificar la clave, elegir un espacio de trabajo, listar sus canales, subir los archivos, crear un borrador con textos por cuenta, programarlo y, por último, leer el estado y los clics. La especificación legible por máquinas es el documento OpenAPI 3.1.
Todo lo que sigue funciona tal cual en cuanto defines dos variables de entorno. Las mismas operaciones existen como herramientas MCP con los mismos nombres, así que lo que aprendas aquí te sirve también en Claude Code, Codex o Cursor.
¿Qué necesitas antes de la primera llamada?
Una clave. O bien una persona la crea en https://app.shager.ai/dashboard/agents con los permisos que necesitas, o bien tu agente crea un entorno de prueba con el registro autónomo. Los permisos funcionan así:
| Permiso | Permite |
|---|---|
read |
whoami, listar espacios de trabajo, canales, grupos y publicaciones, leer una publicación, analítica |
write |
Crear espacios de trabajo, grupos, archivos, borradores, enlaces de conexión y solicitudes de aprobación, y cancelar |
publish |
Programar, incluido create_post con schedule_at |
export SHAGER_API_KEY="shgr_live_your_key_here"
export API="https://app.shager.ai/api/v1"

Llamada 1: ¿quién soy?
curl -s "$API/me" -H "Authorization: Bearer $SHAGER_API_KEY"
{ "data": { "organization_id": "...", "organization": "Northwind Agency", "sandbox": false, "scopes": ["read", "write"] } }
Si sandbox es true, la programación y los enlaces de conexión quedan bloqueados hasta que una persona reclame la clave. Si falta publish en scopes, puedes armar borradores pero no programarlos. Saberlo desde el principio te ahorra un 403 desconcertante más adelante.
Llamada 2: elegir o crear un espacio de trabajo
Un espacio de trabajo suele ser un cliente o una marca.
curl -s "$API/workspaces" -H "Authorization: Bearer $SHAGER_API_KEY"
curl -s -X POST "$API/workspaces" \
-H "Authorization: Bearer $SHAGER_API_KEY" -H "Content-Type: application/json" \
-d '{"name": "Summit Physio", "timezone": "Europe/London", "approval_required": true}'
Define siempre timezone (un nombre IANA) al crearlo. Las horas programadas se guardan con su offset, y la zona del espacio de trabajo es la que usa Shager para mostrárselas a las personas. approval_required: true significa que el cliente tiene que aprobar las publicaciones antes de que se puedan programar, un buen valor por defecto para clientes nuevos.
Exporta el id de la respuesta para que los comandos siguientes funcionen:
export WS="the_workspace_id"
Llamada 3: listar canales y grupos
curl -s "$API/workspaces/$WS/channels" -H "Authorization: Bearer $SHAGER_API_KEY"
curl -s "$API/workspaces/$WS/groups" -H "Authorization: Bearer $SHAGER_API_KEY"
Cada canal viene con id, network, name, handle, status, los formats que admite y connected_by_client. Los grupos devuelven id, name y channel_ids. Con los grupos te diriges a cuarenta cuentas sin listar cuarenta ids: pasa group_ids a create_post y se incluyen todos los canales activos del grupo.
Si el espacio de trabajo todavía no tiene canales, crea un enlace de conexión con POST /workspaces/{workspace_id}/connect-links y envíaselo al dueño de la cuenta. Él mismo conecta Facebook, Instagram, YouTube, X, LinkedIn o TikTok, sin iniciar sesión en Shager.

Llamada 4: subir archivos desde una URL
curl -s -X POST "$API/workspaces/$WS/media" \
-H "Authorization: Bearer $SHAGER_API_KEY" -H "Content-Type: application/json" \
-d '{
"url": "https://cdn.summitphysio.example/video/knee-mobility.mp4",
"file_name": "knee-mobility.mp4",
"width": 1080, "height": 1920, "duration_ms": 42000
}'
Shager descarga el archivo desde el servidor y devuelve id, kind, mime_type y size_bytes. Para Reels, Shorts e historias, pasa width, height y duration_ms si los conoces. Así Shager puede verificar las reglas de formato de cada red antes de programar la publicación, en lugar de fallar en el momento del envío.
Llamada 5: crear un borrador con textos por cuenta
Esta es la llamada que importa. Aquí está en TypeScript con fetch a secas, incluida una pequeña función auxiliar que vas a reutilizar:
const API = "https://app.shager.ai/api/v1";
async function shager<T>(method: string, path: string, body?: unknown): Promise<T> {
const res = await fetch(`${API}${path}`, {
method,
headers: {
Authorization: `Bearer ${process.env.SHAGER_API_KEY}`,
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (!res.ok) {
const err = new Error(`${res.status} ${json.error.code}: ${json.error.message}`);
(err as any).status = res.status;
throw err;
}
return json.data as T;
}
const WS = process.env.SHAGER_WORKSPACE_ID!;
const mediaId = process.env.SHAGER_MEDIA_ID!; // the id returned by call 4
const channels = await shager<Array<{ id: string; name: string; network: string; status: string }>>(
"GET", `/workspaces/${WS}/channels`,
);
const post = await shager<{ id: string; status: string }>("POST", `/workspaces/${WS}/posts`, {
label: "Knee mobility, week 42",
caption: "Three minutes a day for stiffer knees. Full routine in the video.",
channel_ids: channels.map((c) => c.id),
media_ids: [mediaId],
format: "REEL",
youtube_title: "3-minute knee mobility routine",
youtube_privacy: "public",
per_channel: channels.map((c) => ({
channel_id: c.id,
caption: `${c.name}: three minutes a day for stiffer knees. Our physios show the routine.`,
})),
cta_url: "https://summitphysio.example/book",
cta_text: "Book an assessment",
campaign: "knee-mobility-oct",
first_comment: "Book an assessment: https://summitphysio.example/book",
stagger_seconds: [45, 120],
});
console.log(post.id, post.status); // DRAFT
Lo que te da cada campo:
per_channelle da a cada cuenta su propio texto. En la práctica los escribe un agente; la plantilla de arriba ocupa el lugar de ese paso, y el razonamiento está en cómo evitar bloqueos por spam a escala.formatse aplica donde la red lo admite, y en los demás casosSHORToREELse resuelve red por red.youtube_titleyyoutube_privacysolo afectan a los canales de YouTube.cta_urlgenera un enlace corto y un juego de UTM distintos para cada cuenta, conutm_contentidentificando la cuenta. Sobre eso se construye la atribución de leads por cuenta.stagger_secondsdefine una pausa aleatoria entre cuentas de la misma red. El valor por defecto es[30, 90].
Sin schedule_at, el resultado es un borrador. Guarda su id para las llamadas siguientes con export POST_ID="the_post_id".
Llamada 6: aprobación o programación
Si el espacio de trabajo exige aprobación, envíasela al cliente. La respuesta incluye una approval_url que el revisor abre sin iniciar sesión:
curl -s -X POST "$API/workspaces/$WS/posts/$POST_ID/approval" \
-H "Authorization: Bearer $SHAGER_API_KEY" -H "Content-Type: application/json" \
-d '{"reviewer_name": "Dana Ellis", "reviewer_email": "dana@summitphysio.example", "send_email": true}'
Una vez aprobada (o si no hace falta aprobación), prográmala. Esto requiere el permiso publish, y schedule_at tiene que estar en ISO 8601 con offset:
curl -s -X POST "$API/workspaces/$WS/posts/$POST_ID/schedule" \
-H "Authorization: Bearer $SHAGER_API_KEY" -H "Content-Type: application/json" \
-d '{"schedule_at": "2026-10-20T08:30:00+01:00"}'
En este punto, Shager valida cada destino según las reglas de su red. Si alguna cuenta rechazaría la publicación, recibes un 422 rejected con el motivo, y no se encola nada.
Llamada 7: revisar el estado y los clics
curl -s "$API/workspaces/$WS/posts/$POST_ID" -H "Authorization: Bearer $SHAGER_API_KEY"
curl -s "$API/workspaces/$WS/posts?status=SCHEDULED&limit=50" -H "Authorization: Bearer $SHAGER_API_KEY"
curl -s "$API/workspaces/$WS/analytics?from=2026-10-01T00:00:00Z&to=2026-10-31T23:59:59Z" \
-H "Authorization: Bearer $SHAGER_API_KEY"
get_post devuelve una fila por destino con state, dispatch_at, published_at, la url publicada y cualquier error. La analítica devuelve total_clicks, by_account y by_post, y cuenta los clics en los enlaces con seguimiento de Shager sin el tráfico de bots. Para cancelar una publicación programada que todavía no empezó a publicarse: POST /workspaces/{workspace_id}/posts/{post_id}/cancel.
¿Qué errores debería manejar tu código?
| Estado y código | Significado | Qué hacer |
|---|---|---|
401 unauthorized |
Falta el encabezado o está mal formado | Envía Authorization: Bearer shgr_... |
403 forbidden |
A la clave le falta el permiso necesario | Crea un borrador, o pídele publish a una persona |
403 sandbox_unclaimed |
Clave de registro autónomo sin reclamar | Muéstrale el enlace de reclamo a una persona |
404 not_found |
Id de espacio de trabajo, publicación o canal incorrecto | Vuelve a listar; los ids son por espacio de trabajo |
409 not_scheduled |
Cancelar una publicación que no está programada | Lee primero el estado de la publicación |
422 rejected |
Falló una regla de la red | Muestra el mensaje; indica el problema |
429 rate_limited |
Más de 120 solicitudes por minuto por clave | Espera y vuelve a intentar |
El límite de solicitudes rara vez molesta a un cliente bien diseñado. Un solo create_post cubre cientos de cuentas, así que no deberías necesitar una llamada por cuenta.
Preguntas frecuentes
¿Hay un SDK?
No uno propio. El documento OpenAPI 3.1 funciona con los generadores habituales si quieres clientes tipados, y la API es lo bastante pequeña como para que una función auxiliar como la de arriba suela alcanzar. Pronto habrá una CLI de Shager.
¿REST y MCP son de verdad lo mismo?
Sí. Cada herramienta MCP corresponde a una operación REST con el mismo nombre, las mismas entradas, permisos, límite de solicitudes y registro de auditoría. Usa REST desde scripts y servicios, y MCP desde clientes de agentes. La lista completa de herramientas está en la página para agentes.
¿Cómo publico un texto distinto en Facebook y en YouTube dentro de la misma publicación?
Usa per_channel con el id y el texto de cada canal, y youtube_title para el título de YouTube. Un solo objeto de publicación, muchos destinos, cada uno con su propio texto, formato y primer comentario si lo necesitas.
Siguiente paso
Cuando esta secuencia funcione bien a mano, conviértela en algo que un agente pueda repetir. Cómo crear una skill de Claude para Shager muestra cómo empaquetar estas llamadas y las reglas que las rodean en una skill reutilizable.


