# Démarrer avec l'API REST de Shager : d'une clé toute neuve à une publication suivie sur 40 comptes en sept appels

_2026-09-19 · Visite guidée de l'API REST de Shager en curl et TypeScript : authentification, espaces, canaux, médias, brouillons par compte, programmation, statut et clics._

L'API REST de Shager se trouve à l'adresse `https://app.shager.ai/api/v1`, s'authentifie avec `Authorization: Bearer shgr_live_...`, renvoie chaque résultat sous la forme `{ "data": ... }` et chaque erreur sous la forme `{ "error": { "code", "message" } }`. Une diffusion complète tient en sept appels : vérifier la clé, choisir un espace de travail, lister ses canaux, importer le média, créer un brouillon avec une légende par compte, le programmer, puis consulter le statut et les clics. La spécification lisible par machine est le [document OpenAPI 3.1](https://app.shager.ai/api/v1/openapi.json).

Tout ce qui suit fonctionne tel quel dès que vous avez défini deux variables d'environnement. Les mêmes opérations existent sous forme d'outils MCP portant les mêmes noms : ce que vous apprenez ici vaut aussi pour Claude Code, Codex ou Cursor.

## Que faut-il avant le premier appel ?

Une clé. Soit une personne en crée une sur https://app.shager.ai/dashboard/agents avec les portées nécessaires, soit votre agent crée un environnement de test par [inscription autonome](/fr/blog/agent-self-signup-and-claim). Les portées fonctionnent ainsi :

| Portée | Autorise |
|---|---|
| `read` | `whoami`, la liste des espaces, canaux, groupes et publications, la lecture d'une publication, les statistiques |
| `write` | La création d'espaces, de groupes, de médias, de brouillons, de liens de connexion, de demandes de validation, et l'annulation |
| `publish` | La programmation, y compris `create_post` avec `schedule_at` |

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

## Appel 1 : qui suis-je ?

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

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

Si `sandbox` vaut true, la programmation et les liens de connexion sont bloqués tant qu'une personne n'a pas revendiqué la clé. Si `publish` est absent de `scopes`, vous pouvez créer des brouillons mais pas les programmer. Le savoir dès le départ vous épargne plus tard une erreur 403 déroutante.

## Appel 2 : choisir ou créer un espace de travail

Un espace de travail correspond en général à un client ou à une marque.

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

Définissez toujours `timezone` (un nom IANA) à la création. Les horaires de programmation sont stockés avec leur décalage, et c'est le fuseau de l'espace qui sert à les afficher aux personnes. `approval_required: true` signifie que les publications doivent être validées par le client avant de pouvoir être programmées, un bon réglage par défaut pour un nouveau client.

Exportez l'id renvoyé pour que les commandes suivantes fonctionnent :

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

## Appel 3 : lister les canaux et les groupes

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

Chaque canal est renvoyé avec `id`, `network`, `name`, `handle`, `status`, les `formats` qu'il prend en charge et `connected_by_client`. Les groupes renvoient `id`, `name` et `channel_ids`. C'est grâce aux groupes que vous ciblez quarante comptes sans énumérer quarante id : passez `group_ids` à `create_post` et tous les canaux actifs du groupe sont inclus.

Si l'espace n'a pas encore de canaux, créez un lien de connexion avec `POST /workspaces/{workspace_id}/connect-links` et envoyez-le au propriétaire du compte. Il connecte lui-même Facebook, Instagram, YouTube, X, LinkedIn ou TikTok, sans identifiant Shager.

## Appel 4 : importer un média depuis une 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 télécharge le fichier côté serveur et renvoie `id`, `kind`, `mime_type` et `size_bytes`. Pour les Reels, les Shorts et les stories, transmettez `width`, `height` et `duration_ms` si vous les connaissez. Shager peut ainsi vérifier les règles de format de chaque réseau avant la programmation, au lieu d'échouer au moment de l'envoi.

## Appel 5 : créer un brouillon avec une légende par compte

C'est l'appel qui compte. Le voici en TypeScript avec un simple `fetch`, accompagné d'une petite fonction utilitaire que vous réutiliserez :

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

Ce que vous apporte chaque champ :

1. `per_channel` donne à chaque compte sa propre légende. En pratique, c'est un agent qui les rédige ; le modèle ci-dessus tient la place de cette étape, et le raisonnement est expliqué dans [comment éviter les blocages pour spam à grande échelle](/fr/blog/avoid-spam-blocks-when-posting-at-scale).
2. `format` s'applique là où le réseau le prend en charge, et `SHORT` ou `REEL` est sinon résolu réseau par réseau. `youtube_title` et `youtube_privacy` ne concernent que les canaux YouTube.
3. `cta_url` génère un lien court et un jeu d'UTM distincts pour chaque compte, avec un `utm_content` qui identifie le compte. C'est la base de [l'attribution des prospects par compte](/fr/blog/utm-per-account-lead-attribution).
4. `stagger_seconds` définit une pause aléatoire entre les comptes d'un même réseau. La valeur par défaut est `[30, 90]`.

Sans `schedule_at`, le résultat est un brouillon. Gardez son id pour les appels suivants avec `export POST_ID="the_post_id"`.

## Appel 6 : validation ou programmation

Si l'espace exige une validation, envoyez la publication au client. La réponse contient une `approval_url` que le relecteur ouvre sans se connecter :

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

Une fois la publication validée (ou si la validation n'est pas requise), programmez-la. Il faut pour cela la portée `publish`, et `schedule_at` doit être au format ISO 8601 avec un décalage :

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

À cette étape, Shager vérifie chaque destination par rapport aux règles de son réseau. Si un compte risque de rejeter la publication, vous recevez une erreur 422 `rejected` avec la raison, et rien n'est mis en file d'attente.

## Appel 7 : consulter le statut et les clics

```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` renvoie une ligne par destination avec `state`, `dispatch_at`, `published_at`, l'`url` en ligne et l'éventuelle `error`. Les statistiques renvoient `total_clicks`, `by_account` et `by_post`, en comptant les clics sur les liens suivis de Shager, trafic des robots exclu. Pour annuler une publication programmée dont la diffusion n'a pas commencé : `POST /workspaces/{workspace_id}/posts/{post_id}/cancel`.

## Quelles erreurs votre code doit-il gérer ?

| Statut et code | Signification | Que faire |
|---|---|---|
| 401 `unauthorized` | En-tête absent ou mal formé | Envoyer `Authorization: Bearer shgr_...` |
| 403 `forbidden` | La clé n'a pas la portée nécessaire | Créer un brouillon à la place, ou demander `publish` à une personne |
| 403 `sandbox_unclaimed` | Clé d'inscription autonome non revendiquée | Transmettre le lien de revendication à une personne |
| 404 `not_found` | Mauvais id d'espace, de publication ou de canal | Relister ; les id sont propres à chaque espace |
| 409 `not_scheduled` | Annulation d'une publication non programmée | Lire d'abord le statut de la publication |
| 422 `rejected` | Une règle d'un réseau n'est pas respectée | Afficher le message ; il nomme le problème |
| 429 `rate_limited` | Plus de 120 requêtes par minute et par clé | Ralentir et réessayer |

La limite de débit gêne rarement un client bien conçu. Un seul `create_post` couvre des centaines de comptes : vous ne devriez pas avoir besoin d'un appel par compte.

## FAQ

### Existe-t-il un SDK ?

Pas de SDK dédié. Le document OpenAPI 3.1 fonctionne avec les générateurs standard si vous voulez des clients typés, et l'API est assez compacte pour qu'une fonction utilitaire comme celle ci-dessus suffise en général. Une CLI Shager est en préparation.

### REST et MCP, c'est vraiment la même chose ?

Oui. Chaque outil MCP correspond à une opération REST, avec le même nom, les mêmes paramètres, les mêmes portées, la même limite de débit et la même journalisation d'audit. Utilisez REST depuis vos scripts et services, et MCP depuis les clients d'agents. La liste complète des outils se trouve sur la [page des agents](/fr/agents).

### Comment publier un texte différent sur Facebook et sur YouTube dans la même publication ?

Utilisez `per_channel` avec l'id et la légende de chaque canal, et `youtube_title` pour le titre YouTube. Un seul objet publication, de nombreuses destinations, chacune avec sa légende, son format et, si besoin, son premier commentaire.

## Prochaine étape

Quand cette séquence tourne sans accroc à la main, transformez-la en quelque chose qu'un agent peut répéter. [Créer une skill Claude pour Shager](/fr/blog/build-a-claude-skill-for-shager) montre comment regrouper ces appels et les règles qui les encadrent dans une skill réutilisable.

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