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

# Usare l’API pubblica SORANK

> Autenticazione, quote, contenuti e stato delle operazioni.

## Indirizzo di produzione e prima chiamata

Accedi all’app su `https://app.sorank.com`. Gestisci le chiavi su `https://app.sorank.com/settings/api` e invia le richieste a `https://app.sorank.com/api/public/v1`. Non devi creare un server API.

1. Apri **Impostazioni → Accesso API** e crea una chiave con nome, almeno un sito, **Visualizza siti** (`profiles:read`) e scadenza. Aggiungi **Visualizza quote** (`quotas:read`) per la seconda chiamata.
2. Copia subito la chiave completa: viene mostrata una sola volta. Il prefisso visibile in seguito non autentica le richieste.
3. Scegli un esempio. Con cURL, inserisci la chiave quando richiesto. Per JavaScript o Go, imposta `SORANK_API_KEY` nell’ambiente del processo server. La scheda HTML include un server Node.js locale: avvialo con la chiave nell’ambiente e apri `http://127.0.0.1:3100`. Non inserire mai la chiave nell’HTML o nel JavaScript del browser. Aggiungi l’autenticazione della tua app prima di usare questo schema oltre la demo locale.

<Tabs>
  <Tab title="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
    ```
  </Tab>

  <Tab title="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.
    ```
  </Tab>

  <Tab title="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`));
    ```
  </Tab>

  <Tab title="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")))
      }
    }
    ```
  </Tab>
</Tabs>

La risposta elenca i siti autorizzati in `data`. Usa un valore `data[].id` come `profile_id` nelle chiamate successive. Se l’elenco è vuoto, verifica la selezione dei siti e i tuoi permessi. Gli esempi cURL, JavaScript e Go leggono anche la quota del primo profilo con `quotas:read`.

La **Riferimento API pubblica** nella navigazione sviluppatori descrive percorsi, scope e risposte.

## Autenticarsi

Crea una chiave personale in **Impostazioni → Accesso API** per gli script oppure autorizza un client OAuth con PKCE S256. Limita profili e scope al necessario. Invia il token come Bearer. Cookie del browser e token Supabase non autorizzano questa API.

Per un’applicazione che agisce per conto dell’utente, inizia da `https://app.sorank.com/.well-known/oauth-protected-resource/api/public/v1` e `https://app.sorank.com/.well-known/oauth-authorization-server`. Scopri gli endpoint OAuth annunciati, registra un client pubblico se necessario e usa authorization code con PKCE S256, gli scope necessari e la risorsa esatta `https://app.sorank.com/api/public/v1`. Un token OAuth MCP non vale per REST.

## Permessi e quota

Controlla `getQuotas` prima della generazione. L’esempio richiede `profiles:read`, `quotas:read`, `calendar:read`, `calendar:write`, `articles:read`, `articles:generate` e `articles:publish`. La creazione del calendario richiede anche i permessi di generazione e pubblicazione. App, REST e MCP condividono la quota; altre chiavi non la aumentano.

I permessi di lettura **Visualizzare i prodotti** (`products:read`), **Visualizzare le analisi** (`analytics:read`), **Visualizza visibilità IA** (`geo:read`) e **Visualizzare le impostazioni** (`settings:read`) non consumano mai quota. **Modificare le impostazioni** (`settings:write`) modifica le stesse impostazioni dell’app; concedilo solo se un’integrazione deve cambiarle.

## Verificare l’idoneità

L’API è disponibile dopo aver completato l’onboarding in Sorank e se il sito ha un abbonamento valido. Un collaboratore di agenzia usa l’abbonamento del proprietario del sito. Sorank lo verifica alla creazione di una chiave o all’approvazione di una connessione OAuth, e di nuovo a ogni richiesta.

* `listProfiles` restituisce solo i siti idonei.
* Un onboarding non completato restituisce `403 onboarding_required`.
* Un sito senza abbonamento valido restituisce `402 subscription_required`, con `details.reason` (`paused`, `past_due`, `incomplete` o `inactive`) quando noto.

Completa l’onboarding o aggiorna l’abbonamento in **Impostazioni → Fatturazione**. La chiave esistente torna a funzionare dalla richiesta successiva.

## Leggere prodotti, analisi, visibilità IA e impostazioni

Queste letture restituiscono ciò che l’app Sorank ti mostra. Non avviano mai un’analisi, una sincronizzazione o una generazione.

* **Prodotti** (`products:read`): `listProducts` scorre il catalogo prodotti con un cursore. Dopo `409 product_catalog_cursor_stale`, ricomincia dalla prima pagina.
* **Analisi** (`analytics:read`): prestazioni, query, pagine, paesi e dispositivi di Search Console, e traffico Google Analytics proveniente da assistenti IA. `start` e `end` (`YYYY-MM-DD`) sono obbligatori. Ogni chiamata legge Google in tempo reale e ha un limite di frequenza più basso. Collega prima Google in Sorank; altrimenti ricevi `409 gsc_not_connected` o `409 ga_not_connected`, e `403 google_access_lost` se l’accesso è stato perso.
* **Visibilità IA** (`geo:read`): statistiche GEO, evoluzione delle menzioni, cronologia delle citazioni, fonti che citano il sito e concorrenti citati in un’analisi.
* **Impostazioni** (`settings:read`): `getSettings` restituisce il sito, il brief aziendale con la sua `revision`, i concorrenti, le impostazioni predefinite degli articoli, le immagini e il canale YouTube.

## Modificare le impostazioni

Con `settings:write` puoi modificare il nome dell’azienda, il colore del brand e il mercato, sostituire il brief aziendale e l’elenco dei concorrenti, cambiare le impostazioni predefinite degli articoli del calendario, la modalità immagini e le copertine, e collegare, sospendere o scollegare il canale YouTube. L’URL del sito non può essere modificato tramite l’API (`400 field_not_writable`).

Ogni modifica richiede un `Idempotency-Key`. La risposta contiene le impostazioni risultanti, una ricevuta e un elenco `effects` con le attività avviate in background, come nell’app: `geo_questions_regeneration_queued`, `backlink_refresh_queued`, `calendar_slots_shifted`, `image_gallery_analysis_queued`, `youtube_sync_queued` o `youtube_purge_queued`.

Per sostituire il brief aziendale, invia la `expected_revision` letta da `getSettings`. Se nel frattempo il brief è cambiato, ricevi `409 business_brief_revision_conflict`: rileggi le impostazioni prima di riprovare.

## Provare un flusso REST completo

Questo esempio avanzato crea contenuti e può consumare quota. Sostituisci ID, date future, chiavi di idempotenza e credenziale di esempio prima di eseguirlo. Le date del calendario seguono il fuso IANA indicato. Controlla `getCapabilities` prima di scegliere destinazione o categoria CMS.

```sh theme={null}
# Sostituisci gli UUID di esempio con quelli del tuo account.
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"
```

La sezione di riferimento API descrive ogni operazione, campo, risposta ed errore.

## Monitorare e riprovare

Ogni modifica richiede un `Idempotency-Key` univoco. Riutilizza la stessa chiave per ripetere lo stesso comando. Leggi `data.receipt.operation_id` e interroga `getOperation` fino a `succeeded`, `failed` o `verification_pending`. Una ricevuta accettata non significa che la pubblicazione sia completata. Un lotto di generazione di articoli restituisce `202` con una ricevuta già `succeeded`; segui ogni articolo elencato in `selection.accepted_ids` con `getArticle`. L’autorizzazione viene verificata a ogni interrogazione, quindi una revoca ha effetto immediato.

## Gestire gli errori

Ogni errore ha la stessa forma in REST e MCP: `{"error": {"code", "message", "request_id", "retryable", "details"}}`. Basa la tua logica su `code`, non sul messaggio. Riprova solo se `retryable` è `true`, dopo `Retry-After` quando presente.

* `401 invalid_credential`: credenziale non valida, scaduta o revocata.
* `403 missing_scope` o `403 onboarding_required`; `402 subscription_required` o `402 insufficient_quota`.
* `404 not_found`: la risorsa non esiste o non è accessibile con questa credenziale.
* `409`: un conflitto come `idempotency_conflict`; `429 rate_limited`: attendi `Retry-After`.
* `503 auth_state_unavailable`: l’accesso resta negato finché lo stato di autorizzazione non torna disponibile; riprova più tardi.

Il riferimento dell’API pubblica elenca i codici di ogni operazione. Condividi `code` e `request_id` con il supporto, mai il tuo token.

Per collegare un assistente, leggi la guida MCP.


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