> ## Documentation Index
> Fetch the complete documentation index at: https://sorank.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Die öffentliche SORANK API verwenden

> Authentifizierung, Kontingente, Inhalte und API-Vorgänge.

## Produktionsadresse und erster Aufruf

Melden Sie sich in der App unter `https://app.sorank.com` an. Ihre Schlüssel verwalten Sie unter `https://app.sorank.com/settings/api`; die REST-Basisadresse lautet `https://app.sorank.com/api/public/v1`. Sie müssen keinen API-Server erstellen.

1. Öffnen Sie **Einstellungen → API-Zugriff** und erstellen Sie einen Schlüssel mit Namen, mindestens einer Website, **Websites anzeigen** (`profiles:read`) und Ablaufdatum. Wählen Sie zusätzlich **Kontingente anzeigen** (`quotas:read`) für den zweiten Aufruf.
2. Kopieren Sie den vollständigen Schlüssel sofort; er wird nur einmal angezeigt. Der später sichtbare Präfix reicht zur Anmeldung nicht aus.
3. Wählen Sie ein Beispiel. Bei cURL geben Sie den Schlüssel auf Nachfrage ein. Für JavaScript oder Go setzen Sie `SORANK_API_KEY` in der Umgebung des Serverprozesses. Der HTML-Tab enthält auch einen lokalen Node.js-Server: Starten Sie ihn mit dem Schlüssel in seiner Umgebung und öffnen Sie `http://127.0.0.1:3100`. Der Schlüssel gehört niemals in HTML oder Browser-JavaScript. Ergänzen Sie vor einem Einsatz außerhalb dieser lokalen Demo die Anmeldung Ihrer Anwendung.

**cURL**

```sh theme={null}
printf 'SORANK API key: '; read -rs SORANK_API_KEY; echo
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $SORANK_API_KEY" \
  'https://app.sorank.com/api/public/v1/profiles'

# Copy an authorized data[].id from the response above.
PROFILE_ID='<profile_id>'
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $SORANK_API_KEY" \
  "https://app.sorank.com/api/public/v1/profiles/$PROFILE_ID/quotas"
unset SORANK_API_KEY
```

**HTML**

```html index.html theme={null}
<button id="load">Load my SORANK websites</button>
<pre id="result"></pre>
<script>
  document.querySelector('#load').addEventListener('click', async () => {
    const output = document.querySelector('#result');
    try {
      // Your own backend authenticates to SORANK. Keep the key out of this page.
      const response = await fetch('/api/sorank/profiles');
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      const profiles = await response.json();
      output.textContent = JSON.stringify(profiles, null, 2);
    } catch (error) {
      output.textContent = String(error);
    }
  });
</script>
```

```js server.mjs theme={null}
// Local demo backend. Run with SORANK_API_KEY in the environment.
import { createServer } from 'node:http';
import { readFile } from 'node:fs/promises';

const key = process.env.SORANK_API_KEY;
if (!key) throw new Error('Set SORANK_API_KEY before running this file');
const html = await readFile(new URL('./index.html', import.meta.url));

createServer(async (request, response) => {
  if (request.method === 'GET' && request.url === '/') {
    response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
    response.end(html);
    return;
  }
  if (request.method === 'GET' && request.url === '/api/sorank/profiles') {
    try {
      const upstream = await fetch('https://app.sorank.com/api/public/v1/profiles', {
        headers: { Authorization: `Bearer ${key}` },
      });
      response.writeHead(upstream.status, {
        'Content-Type': 'application/json; charset=utf-8',
        'Cache-Control': 'no-store',
      });
      response.end(await upstream.text());
    } catch {
      response.writeHead(502).end('Upstream unavailable');
    }
    return;
  }
  response.writeHead(404).end('Not found');
}).listen(3100, '127.0.0.1');
// Open http://127.0.0.1:3100 after starting this server.
```

**JavaScript**

```js profiles.mjs theme={null}
// Run on Node.js 20+ with SORANK_API_KEY set in the process environment.
const token = process.env.SORANK_API_KEY;
if (!token) throw new Error('Set SORANK_API_KEY before running this file');
const base = 'https://app.sorank.com/api/public/v1';

async function get(path) {
  const response = await fetch(`${base}${path}`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  const body = await response.json();
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${JSON.stringify(body)}`);
  return body;
}

const profiles = await get('/profiles');
console.log(profiles);
const profileId = profiles.data[0]?.id;
if (profileId) console.log(await get(`/profiles/${encodeURIComponent(profileId)}/quotas`));
```

**Go**

```go main.go theme={null}
package main

import (
  "encoding/json"
  "fmt"
  "io"
  "net/http"
  "os"
  "time"
)

func main() {
  key := os.Getenv("SORANK_API_KEY")
  if key == "" { panic("set SORANK_API_KEY before running this file") }
  client := &http.Client{Timeout: 15 * time.Second}
  get := func(path string) []byte {
    req, err := http.NewRequest(http.MethodGet, "https://app.sorank.com/api/public/v1"+path, nil)
    if err != nil { panic(err) }
    req.Header.Set("Authorization", "Bearer "+key)
    resp, err := client.Do(req)
    if err != nil { panic(err) }
    defer resp.Body.Close()
    body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
    if err != nil { panic(err) }
    if resp.StatusCode != http.StatusOK { panic(fmt.Sprintf("HTTP %d: %s", resp.StatusCode, body)) }
    return body
  }

  body := get("/profiles")
  fmt.Println(string(body))
  var profiles struct { Data []struct { ID string `json:"id"` } `json:"data"` }
  if err := json.Unmarshal(body, &profiles); err != nil { panic(err) }
  if len(profiles.Data) > 0 {
    fmt.Println(string(get("/profiles/"+profiles.Data[0].ID+"/quotas")))
  }
}
```

Die Antwort enthält die freigegebenen Websites unter `data`. Verwenden Sie eine `data[].id` als `profile_id` für weitere Aufrufe. Ist die Liste leer, prüfen Sie die Website-Auswahl und Ihre Zugriffsrechte. Die Beispiele für cURL, JavaScript und Go lesen mit `quotas:read` auch das Kontingent des ersten Profils.

Die **öffentliche API-Referenz** in der Entwicklernavigation enthält alle Pfade, erforderlichen Scopes und Antwortformate.

## Authentifizieren

Erstellen Sie für Skripte unter **Einstellungen → API-Zugriff** einen persönlichen API-Schlüssel oder autorisieren Sie einen OAuth-Client mit PKCE S256. Begrenzen Sie Profile und Scopes auf das Nötige. Senden Sie das Token als Bearer-Token. Browser-Cookies und Supabase-Token gelten hier nicht.

Für Anwendungen im Namen eines Nutzers beginnen Sie mit `https://app.sorank.com/.well-known/oauth-protected-resource/api/public/v1` und `https://app.sorank.com/.well-known/oauth-authorization-server`. Ermitteln Sie die angegebenen OAuth-Endpunkte, registrieren Sie bei Bedarf einen öffentlichen Client und verwenden Sie Authorization Code mit PKCE S256, benötigten Scopes und der exakten Ressource `https://app.sorank.com/api/public/v1`. Ein MCP OAuth-Token gilt nicht für REST.

## Berechtigungen und Kontingent

Prüfen Sie vor der Generierung `getQuotas`. Das Beispiel benötigt `profiles:read`, `quotas:read`, `calendar:read`, `calendar:write`, `articles:read`, `articles:generate` und `articles:publish`. Für das Erstellen eines Kalenders sind auch Generierungs- und Veröffentlichungsrechte nötig. App, REST und MCP teilen sich das Kontingent; weitere Schlüssel erhöhen es nicht.

Die Leseberechtigungen **Produkte anzeigen** (`products:read`), **Analysen anzeigen** (`analytics:read`), **KI-Sichtbarkeit anzeigen** (`geo:read`) und **Einstellungen anzeigen** (`settings:read`) verbrauchen nie Kontingent. **Einstellungen bearbeiten** (`settings:write`) ändert dieselben Einstellungen wie die App; erteilen Sie sie nur, wenn eine Integration diese ändern soll.

## Voraussetzungen prüfen

Die API ist verfügbar, sobald Sie das Onboarding in Sorank abgeschlossen haben und die Website ein gültiges Abonnement hat. Ein Agentur-Mitarbeiter nutzt das Abonnement des Website-Inhabers. Sorank prüft dies beim Erstellen eines Schlüssels oder beim Bestätigen einer OAuth-Verbindung und erneut bei jeder Anfrage.

* `listProfiles` liefert nur berechtigte Websites.
* Ein nicht abgeschlossenes Onboarding ergibt `403 onboarding_required`.
* Eine Website ohne gültiges Abonnement ergibt `402 subscription_required`, mit `details.reason` (`paused`, `past_due`, `incomplete` oder `inactive`), sofern bekannt.

Schließen Sie das Onboarding ab oder aktualisieren Sie das Abonnement unter **Einstellungen → Abrechnung**. Ihr bestehender Schlüssel funktioniert ab der nächsten Anfrage wieder.

## Produkte, Analysen, KI-Sichtbarkeit und Einstellungen lesen

Diese Lesezugriffe liefern, was Ihnen die Sorank-App anzeigt. Sie starten nie eine Analyse, Synchronisierung oder Generierung.

* **Produkte** (`products:read`): `listProducts` blättert mit einem Cursor durch den Produktkatalog. Beginnen Sie nach `409 product_catalog_cursor_stale` wieder mit der ersten Seite.
* **Analysen** (`analytics:read`): Leistung, Suchanfragen, Seiten, Länder und Geräte aus der Search Console sowie Google-Analytics-Traffic von KI-Assistenten. `start` und `end` (`YYYY-MM-DD`) sind Pflicht. Jeder Aufruf liest live bei Google und hat ein niedrigeres Ratenlimit. Verbinden Sie Google zuerst in Sorank; sonst erhalten Sie `409 gsc_not_connected` oder `409 ga_not_connected` und bei verlorenem Zugriff `403 google_access_lost`.
* **KI-Sichtbarkeit** (`geo:read`): GEO-Statistiken, Entwicklung der Erwähnungen, Zeitverlauf der Zitate, zitierende Quellen und die in einer Analyse zitierten Wettbewerber.
* **Einstellungen** (`settings:read`): `getSettings` liefert die Website, das Business-Briefing mit seiner `revision`, Wettbewerber, Artikel-Standards, Bilder und den YouTube-Kanal.

## Einstellungen ändern

Mit `settings:write` können Sie Firmenname, Markenfarbe und Markt ändern, das Business-Briefing und die Wettbewerberliste ersetzen, die Artikel-Standards des Kalenders, den Bildmodus und die Titelbilder ändern sowie den YouTube-Kanal verbinden, pausieren oder trennen. Die Website-URL lässt sich über die API nicht ändern (`400 field_not_writable`).

Jede Änderung erfordert einen `Idempotency-Key`. Die Antwort enthält die resultierenden Einstellungen, einen Beleg und eine Liste `effects` mit den im Hintergrund gestarteten Vorgängen, wie in der App: `geo_questions_regeneration_queued`, `backlink_refresh_queued`, `calendar_slots_shifted`, `image_gallery_analysis_queued`, `youtube_sync_queued` oder `youtube_purge_queued`.

Senden Sie zum Ersetzen des Business-Briefings die in `getSettings` gelesene `expected_revision`. Hat sich das Briefing inzwischen geändert, erhalten Sie `409 business_brief_revision_conflict`: Lesen Sie die Einstellungen erneut, bevor Sie es wiederholen.

## Einen REST-Ablauf ausprobieren

Dieses erweiterte Beispiel erstellt Inhalte und kann Kontingent verbrauchen. Ersetzen Sie UUIDs, Daten, Idempotenzschlüssel und den Beispielschlüssel. Kalenderdaten richten sich nach der angegebenen IANA-Zeitzone. Prüfen Sie `getCapabilities`, bevor Sie CMS-Ziel oder Kategorie wählen.

```sh theme={null}
# Ersetzen Sie Beispiel-UUIDs durch IDs aus Ihrem Konto.
API_ORIGIN="https://app.sorank.com"
PROFILE_ID="00000000-0000-4000-8000-000000000000"
ARTICLE_ID="11111111-1111-4111-8111-111111111111"
TOKEN="<PERSONAL_API_KEY>"

curl -H "Authorization: Bearer $TOKEN" "$API_ORIGIN/api/public/v1/profiles/$PROFILE_ID/quotas"
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: 22222222-2222-4222-8222-222222222222" \
  -d '{"settings":{"timezone":"Europe/Paris","local_time":"09:30"},"start_date":"2026-11-01","end_date":"2026-12-31","subjects":[{"subject":"Sustainable travel checklist"}]}' \
  "$API_ORIGIN/api/public/v1/profiles/$PROFILE_ID/calendar"
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: 33333333-3333-4333-8333-333333333333" \
  -d '{"seeds":[{"keyword":"sustainable travel tips","title":"Practical sustainable travel"}]}' \
  "$API_ORIGIN/api/public/v1/profiles/$PROFILE_ID/article-generations"
curl -H "Authorization: Bearer $TOKEN" "$API_ORIGIN/api/public/v1/operations/<OPERATION_UUID>"
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: 44444444-4444-4444-8444-444444444444" \
  -d '{"target":"draft"}' "$API_ORIGIN/api/public/v1/profiles/$PROFILE_ID/articles/$ARTICLE_ID/publications"
```

Die separate API-Referenz enthält alle Operationen, Felder, Antworten und Fehler.

## Status und Wiederholungen

Jede Änderung benötigt einen eindeutigen `Idempotency-Key`. Verwenden Sie denselben Schlüssel, wenn Sie denselben Befehl wiederholen. Lesen Sie `data.receipt.operation_id` und fragen Sie `getOperation` ab, bis `succeeded`, `failed` oder `verification_pending` erreicht ist. Ein angenommener Beleg bedeutet nicht, dass die Veröffentlichung abgeschlossen ist. Ein Artikel-Generierungsauftrag liefert `202` mit einem bereits `succeeded` Beleg; verfolgen Sie jeden in `selection.accepted_ids` aufgeführten Artikel mit `getArticle`. Die Berechtigung wird bei jeder Abfrage erneut geprüft, ein Widerruf wirkt sofort.

## Fehler behandeln

Jeder Fehler hat in REST und MCP dieselbe Form: `{"error": {"code", "message", "request_id", "retryable", "details"}}`. Richten Sie Ihre Logik nach `code`, nicht nach der Meldung. Wiederholen Sie nur, wenn `retryable` den Wert `true` hat, und warten Sie `Retry-After` ab, falls vorhanden.

* `401 invalid_credential`: ungültige, abgelaufene oder widerrufene Zugangsdaten.
* `403 missing_scope` oder `403 onboarding_required`; `402 subscription_required` oder `402 insufficient_quota`.
* `404 not_found`: Die Ressource fehlt oder ist für diese Zugangsdaten nicht zugänglich.
* `409`: ein Konflikt wie `idempotency_conflict`; `429 rate_limited`: Warten Sie `Retry-After` ab.
* `503 auth_state_unavailable`: Der Zugriff bleibt verweigert, bis der Autorisierungsstatus wieder verfügbar ist; versuchen Sie es später erneut.

Die Referenz der öffentlichen API listet die Codes jeder Operation. Geben Sie dem Support `code` und `request_id`, niemals Ihr Token.

Für Assistenten lesen Sie den MCP-Leitfaden.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.