# Quickstart della REST API di Shager: da una chiave nuova a un post tracciato su 40 account in sette chiamate

_2026-09-19 · La REST API di Shager in pratica, con curl e TypeScript: autenticazione, workspace, canali, media, bozze per account, programmazione, stato e clic._

La REST API di Shager si trova su `https://app.shager.ai/api/v1`, si autentica con `Authorization: Bearer shgr_live_...`, restituisce ogni risultato come `{ "data": ... }` e ogni errore come `{ "error": { "code", "message" } }`. Una distribuzione completa richiede sette chiamate: verificare la chiave, scegliere un workspace, elencarne i canali, caricare i media, creare una bozza con testi diversi per account, programmarla, poi leggere stato e clic. La specifica leggibile dalle macchine è il [documento OpenAPI 3.1](https://app.shager.ai/api/v1/openapi.json).

Tutto quello che segue funziona così com'è, una volta impostate due variabili d'ambiente. Le stesse operazioni esistono come strumenti MCP con gli stessi nomi, quindi ciò che impari qui vale anche per Claude Code, Codex o Cursor.

## Cosa ti serve prima della prima chiamata?

Una chiave. Può crearla una persona su https://app.shager.ai/dashboard/agents con gli ambiti necessari, oppure il tuo agente può creare un ambiente di prova con la [registrazione autonoma](/en/blog/agent-self-signup-and-claim). Gli ambiti funzionano così:

| Ambito | Consente |
|---|---|
| `read` | `whoami`, elenco di workspace, canali, gruppi e post, lettura di un post, analytics |
| `write` | Creazione di workspace, gruppi, media, bozze, link di collegamento, richieste di approvazione, annullamenti |
| `publish` | Programmazione, compreso `create_post` con `schedule_at` |

```bash
export SHAGER_API_KEY="shgr_live_your_key_here"
export API="https://app.shager.ai/api/v1"
```

## Chiamata 1: chi sono?

```bash
curl -s "$API/me" -H "Authorization: Bearer $SHAGER_API_KEY"
```

```json
{ "data": { "organization_id": "...", "organization": "Northwind Agency", "sandbox": false, "scopes": ["read", "write"] } }
```

Se `sandbox` è true, la programmazione e i link di collegamento restano bloccati finché una persona non rivendica la chiave. Se in `scopes` manca `publish`, puoi preparare bozze ma non programmarle. Saperlo subito ti evita più avanti un 403 difficile da interpretare.

## Chiamata 2: scegli o crea un workspace

Di solito un workspace corrisponde a un cliente o a un brand.

```bash
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}'
```

Imposta sempre `timezone` (un nome IANA) al momento della creazione. Gli orari di programmazione vengono salvati con il loro offset, e il fuso del workspace determina come Shager li mostra alle persone. `approval_required: true` significa che i post devono essere approvati dal cliente prima di poter essere programmati: un buon default per i clienti nuovi.

Esporta l'id dalla risposta, così i comandi successivi funzionano:

```bash
export WS="the_workspace_id"
```

## Chiamata 3: elenca canali e gruppi

```bash
curl -s "$API/workspaces/$WS/channels" -H "Authorization: Bearer $SHAGER_API_KEY"
curl -s "$API/workspaces/$WS/groups" -H "Authorization: Bearer $SHAGER_API_KEY"
```

Ogni canale restituisce `id`, `network`, `name`, `handle`, `status`, i `formats` che supporta e `connected_by_client`. I gruppi restituiscono `id`, `name` e `channel_ids`. Con i gruppi raggiungi quaranta account senza elencare quaranta id: passa `group_ids` a `create_post` e tutti i canali attivi del gruppo vengono inclusi.

Se il workspace non ha ancora canali, crea un link di collegamento con `POST /workspaces/{workspace_id}/connect-links` e invialo al titolare dell'account. Sarà lui a collegare Facebook, Instagram, YouTube, X, LinkedIn o TikTok, senza accedere a Shager.

## Chiamata 4: carica i media da un URL

```bash
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 scarica il file lato server e restituisce `id`, `kind`, `mime_type` e `size_bytes`. Per Reel, Short e storie, passa `width`, `height` e `duration_ms` quando li conosci. Così Shager può verificare le regole di formato di ogni social prima che il post venga programmato, invece di fallire al momento dell'invio.

## Chiamata 5: crea una bozza con testi diversi per account

È la chiamata che conta davvero. Eccola in TypeScript con un semplice `fetch`, insieme a una piccola funzione di supporto che riutilizzerai:

```typescript
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
```

Cosa ti dà ciascun campo:

1. `per_channel` assegna a ogni account il proprio testo. In pratica li scrive un agente; il modello qui sopra è solo un segnaposto per quel passaggio, e il perché è spiegato in [come evitare i blocchi per spam quando pubblichi su larga scala](/en/blog/avoid-spam-blocks-when-posting-at-scale).
2. `format` viene applicato dove il social lo supporta, altrimenti la scelta tra `SHORT` e `REEL` viene risolta social per social. `youtube_title` e `youtube_privacy` riguardano solo i canali YouTube.
3. `cta_url` genera un link breve e un set di UTM separati per ogni account, con `utm_content` che identifica l'account. È la base dell'[attribuzione dei contatti per account](/en/blog/utm-per-account-lead-attribution).
4. `stagger_seconds` imposta una pausa casuale tra gli account dello stesso social. Il valore predefinito è `[30, 90]`.

Senza `schedule_at`, il risultato è una bozza. Conserva il suo id per le chiamate successive con `export POST_ID="the_post_id"`.

## Chiamata 6: approvazione o programmazione

Se il workspace richiede l'approvazione, invia la bozza al cliente. La risposta contiene un `approval_url` che il revisore apre senza accedere:

```bash
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 volta approvata (o se l'approvazione non è richiesta), programmala. Serve l'ambito `publish`, e `schedule_at` deve essere in formato ISO 8601 con offset:

```bash
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"}'
```

A questo punto Shager verifica ogni destinazione rispetto alle regole del suo social. Se anche un solo account rifiuterebbe il post, ricevi un 422 `rejected` con il motivo, e nulla viene messo in coda.

## Chiamata 7: controlla stato e clic

```bash
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` restituisce una riga per destinazione con `state`, `dispatch_at`, `published_at`, l'`url` pubblicato ed eventuali `error`. Gli analytics restituiscono `total_clicks`, `by_account` e `by_post`, contando i clic sui link tracciati di Shager con il traffico dei bot escluso. Per annullare un post programmato la cui pubblicazione non è ancora iniziata: `POST /workspaces/{workspace_id}/posts/{post_id}/cancel`.

## Quali errori deve gestire il tuo codice?

| Stato e codice | Significato | Cosa fare |
|---|---|---|
| 401 `unauthorized` | Header mancante o malformato | Invia `Authorization: Bearer shgr_...` |
| 403 `forbidden` | Alla chiave manca l'ambito necessario | Crea una bozza, oppure chiedi a una persona il permesso `publish` |
| 403 `sandbox_unclaimed` | Chiave da registrazione autonoma non rivendicata | Mostra il link di rivendicazione a una persona |
| 404 `not_found` | Id di workspace, post o canale errato | Rifai l'elenco: gli id sono per workspace |
| 409 `not_scheduled` | Annullamento di un post non programmato | Leggi prima lo stato del post |
| 422 `rejected` | Una regola del social non è rispettata | Mostra il messaggio: indica il problema |
| 429 `rate_limited` | Oltre 120 richieste al minuto per chiave | Rallenta e riprova |

Un client ben progettato raramente si scontra con il limite di richieste. Un solo `create_post` copre centinaia di account, quindi non dovrebbe servirti una chiamata per account.

## Domande frequenti

### Esiste un SDK?

Non uno dedicato. Il documento OpenAPI 3.1 funziona con i generatori standard se vuoi client tipizzati, e l'API è abbastanza piccola che di solito basta una funzione di supporto come quella qui sopra. È in arrivo una CLI di Shager.

### REST e MCP sono davvero la stessa cosa?

Sì. Ogni strumento MCP corrisponde a un'operazione REST con lo stesso nome, gli stessi input, gli stessi ambiti, lo stesso limite di richieste e lo stesso registro delle chiamate. Usa REST da script e servizi, MCP dai client per agenti. L'elenco completo degli strumenti è nella [pagina per agenti](/en/agents).

### Come pubblico testi diversi su Facebook e YouTube nello stesso post?

Usa `per_channel` con l'id e il testo di ogni canale, e `youtube_title` per il titolo su YouTube. Un solo oggetto post, tante destinazioni, ognuna con il suo testo, il suo formato e, se serve, il suo primo commento.

## Il prossimo passo

Quando questa sequenza funziona senza intoppi a mano, trasformala in qualcosa che un agente possa ripetere. [Creare una skill di Claude per Shager](/en/blog/build-a-claude-skill-for-shager) mostra come raccogliere queste chiamate e le regole che le accompagnano in una skill riutilizzabile.

---
Source: https://shager.ai/it/blog/shager-rest-api-quickstart
