# Schnellstart für Shagers REST-API: vom frischen Schlüssel zum getrackten Beitrag auf 40 Konten in sieben Aufrufen

_2026-09-19 · Shagers REST-API in curl und TypeScript: Anmeldung, Workspaces, Kanäle, Medien, Entwürfe pro Konto, Planung, Statusabfrage und Klicks._

Shagers REST-API liegt unter `https://app.shager.ai/api/v1`, authentifiziert mit `Authorization: Bearer shgr_live_...`, liefert jedes Ergebnis als `{ "data": ... }` und jeden Fehler als `{ "error": { "code", "message" } }`. Ein kompletter Verteilungsdurchlauf besteht aus sieben Aufrufen: Schlüssel prüfen, Workspace wählen, Kanäle auflisten, Medien hochladen, einen Entwurf mit Texten pro Konto anlegen, ihn planen, dann Status und Klicks abfragen. Die maschinenlesbare Spezifikation ist das [OpenAPI-3.1-Dokument](https://app.shager.ai/api/v1/openapi.json).

Alles Folgende läuft genau so, wie es dasteht, sobald du zwei Umgebungsvariablen gesetzt hast. Dieselben Operationen gibt es als MCP-Tools mit denselben Namen – was du hier lernst, gilt also auch für Claude Code, Codex oder Cursor.

## Was brauchst du vor dem ersten Aufruf?

Einen Schlüssel. Entweder erstellt ihn ein Mensch unter https://app.shager.ai/dashboard/agents mit den Scopes, die du brauchst, oder dein Agent legt per [Selbstregistrierung](/en/blog/agent-self-signup-and-claim) eine Sandbox an. Die Scopes funktionieren so:

| Scope | Erlaubt |
|---|---|
| `read` | `whoami`, Workspaces, Kanäle, Gruppen und Beiträge auflisten, einen Beitrag lesen, Analytics |
| `write` | Workspaces, Gruppen, Medien, Entwürfe, Verbindungslinks und Freigabeanfragen anlegen, stornieren |
| `publish` | Planen, einschließlich `create_post` mit `schedule_at` |

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

## Aufruf 1: Wer bin ich?

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

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

Ist `sandbox` true, sind Planen und Verbindungslinks gesperrt, bis ein Mensch den Schlüssel übernimmt. Fehlt `publish` in `scopes`, kannst du Entwürfe bauen, aber nicht planen. Wer das vorher weiß, erspart sich später einen rätselhaften 403.

## Aufruf 2: Workspace wählen oder anlegen

Ein Workspace ist meist ein Kunde oder eine Marke.

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

Setz beim Anlegen immer `timezone` (einen IANA-Namen). Geplante Zeiten werden mit ihrem Offset gespeichert, und über die Zone des Workspace zeigt Shager sie Menschen an. `approval_required: true` bedeutet, dass der Kunde Beiträge freigeben muss, bevor sie geplant werden können – ein guter Standard für neue Kunden.

Exportiere die ID aus der Antwort, damit die folgenden Befehle funktionieren:

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

## Aufruf 3: Kanäle und Gruppen auflisten

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

Jeder Kanal kommt mit `id`, `network`, `name`, `handle`, `status`, den unterstützten `formats` und `connected_by_client` zurück. Gruppen liefern `id`, `name` und `channel_ids`. Über Gruppen sprichst du vierzig Konten an, ohne vierzig IDs aufzuzählen: Übergib `group_ids` an `create_post`, und jeder aktive Kanal der Gruppe ist dabei.

Hat der Workspace noch keine Kanäle, erstelle mit `POST /workspaces/{workspace_id}/connect-links` einen Verbindungslink und schick ihn an den Kontoinhaber. Der verbindet Facebook, Instagram, YouTube, X, LinkedIn oder TikTok selbst, ganz ohne Shager-Login.

## Aufruf 4: Medien von einer URL hochladen

```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 lädt die Datei serverseitig herunter und gibt `id`, `kind`, `mime_type` und `size_bytes` zurück. Übergib für Reels, Shorts und Storys `width`, `height` und `duration_ms`, wenn du sie kennst. So kann Shager die Formatregeln jedes Netzwerks prüfen, bevor ein Beitrag geplant wird, statt erst beim Versand zu scheitern.

## Aufruf 5: einen Entwurf mit Texten pro Konto anlegen

Das ist der entscheidende Aufruf. Hier in TypeScript mit schlichtem `fetch`, inklusive einer kleinen Hilfsfunktion, die du wiederverwenden wirst:

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

Was dir jedes Feld bringt:

1. `per_channel` gibt jedem Konto seinen eigenen Text. In der Praxis schreibt die ein Agent; die Vorlage oben ist nur ein Platzhalter für diesen Schritt. Die Begründung steht in [Spam-Sperren in großem Maßstab vermeiden](/en/blog/avoid-spam-blocks-when-posting-at-scale).
2. `format` wird angewendet, wo das Netzwerk es unterstützt; ansonsten wird `SHORT` oder `REEL` pro Netzwerk aufgelöst. `youtube_title` und `youtube_privacy` wirken sich nur auf YouTube-Kanäle aus.
3. `cta_url` erzeugt pro Konto einen eigenen Kurzlink und einen eigenen UTM-Satz, wobei `utm_content` das Konto kennzeichnet. Darauf baut die [Lead-Zuordnung pro Konto](/en/blog/utm-per-account-lead-attribution) auf.
4. `stagger_seconds` legt eine zufällige Pause zwischen Konten desselben Netzwerks fest. Standard ist `[30, 90]`.

Ohne `schedule_at` entsteht ein Entwurf. Merk dir seine ID für die nächsten Aufrufe mit `export POST_ID="the_post_id"`.

## Aufruf 6: Freigabe oder Planung

Verlangt der Workspace eine Freigabe, schick den Beitrag an den Kunden. Die Antwort enthält eine `approval_url`, die der Prüfer ohne Login öffnet:

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

Nach der Freigabe (oder wenn keine nötig ist) planst du den Beitrag. Dafür braucht es den Scope `publish`, und `schedule_at` muss ISO 8601 mit Offset sein:

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

An dieser Stelle prüft Shager jedes Ziel gegen die Regeln seines Netzwerks. Würde ein Konto den Beitrag ablehnen, bekommst du einen 422 `rejected` mit Begründung, und nichts landet in der Warteschlange.

## Aufruf 7: Status und Klicks abfragen

```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` liefert eine Zeile pro Ziel mit `state`, `dispatch_at`, `published_at`, der Live-`url` und gegebenenfalls einem `error`. Analytics liefert `total_clicks`, `by_account` und `by_post` und zählt die Klicks auf Shagers Tracking-Links, Bot-Traffic ausgenommen. Einen geplanten Beitrag, dessen Veröffentlichung noch nicht begonnen hat, stornierst du mit `POST /workspaces/{workspace_id}/posts/{post_id}/cancel`.

## Welche Fehler sollte dein Code abfangen?

| Status und Code | Bedeutung | Was tun |
|---|---|---|
| 401 `unauthorized` | Header fehlt oder ist fehlerhaft | `Authorization: Bearer shgr_...` senden |
| 403 `forbidden` | Dem Schlüssel fehlt der nötige Scope | Stattdessen einen Entwurf anlegen oder einen Menschen um `publish` bitten |
| 403 `sandbox_unclaimed` | Nicht übernommener Schlüssel aus der Selbstregistrierung | Den Übernahme-Link einem Menschen zeigen |
| 404 `not_found` | Falsche Workspace-, Beitrags- oder Kanal-ID | Neu auflisten; IDs gelten pro Workspace |
| 409 `not_scheduled` | Stornierung eines Beitrags, der nicht geplant ist | Zuerst den Status des Beitrags lesen |
| 422 `rejected` | Eine Netzwerkregel ist verletzt | Die Meldung anzeigen; sie benennt das Problem |
| 429 `rate_limited` | Mehr als 120 Anfragen pro Minute und Schlüssel | Kurz warten und erneut versuchen |

Ein gut gebauter Client stößt selten ans Rate-Limit. Ein einziges `create_post` deckt Hunderte Konten ab, du brauchst also keinen Aufruf pro Konto.

## FAQ

### Gibt es ein SDK?

Kein eigenes. Das OpenAPI-3.1-Dokument funktioniert mit gängigen Generatoren, falls du typisierte Clients willst, und die API ist so überschaubar, dass eine Hilfsfunktion wie die oben meist völlig reicht. Eine Shager-CLI ist in Arbeit.

### Sind REST und MCP wirklich dasselbe?

Ja. Jedes MCP-Tool entspricht genau einer REST-Operation mit demselben Namen, denselben Eingaben, Scopes, demselben Rate-Limit und derselben Audit-Protokollierung. Nutze REST aus Skripten und Diensten, MCP aus Agenten-Clients. Die vollständige Tool-Liste steht auf der [Seite für Agenten](/en/agents).

### Wie poste ich im selben Beitrag unterschiedlichen Text auf Facebook und YouTube?

Mit `per_channel`, jeweils mit ID und Text des Kanals, und mit `youtube_title` für den YouTube-Titel. Ein Beitragsobjekt, viele Ziele – jedes mit eigenem Text, eigenem Format und, falls nötig, eigenem ersten Kommentar.

## Nächster Schritt

Wenn diese Abfolge von Hand sauber läuft, pack sie in etwas, das ein Agent wiederholen kann. [Einen Claude-Skill für Shager bauen](/en/blog/build-a-claude-skill-for-shager) zeigt, wie du diese Aufrufe und die Regeln drumherum in einen wiederverwendbaren Skill packst.

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