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

# Utilizar a API pública da SORANK

> Autenticação, quotas, conteúdo e acompanhamento de operações.

## Endereço de produção e primeiro pedido

Entre na aplicação em `https://app.sorank.com`. Faça a gestão das chaves em `https://app.sorank.com/settings/api` e envie pedidos para `https://app.sorank.com/api/public/v1`. Não precisa de criar um servidor API.

1. Abra **Definições → Acesso à API** e crie uma chave com nome, pelo menos um site, **Ver sites** (`profiles:read`) e validade. Adicione **Ver cotas** (`quotas:read`) para o segundo pedido.
2. Copie já a chave completa: só aparece uma vez. O prefixo mostrado depois não autentica pedidos.
3. Escolha um exemplo. Com cURL, introduza a chave quando solicitada. Para JavaScript ou Go, defina `SORANK_API_KEY` no ambiente do processo servidor. O separador HTML inclui um servidor Node.js local: execute-o com a chave no ambiente e abra `http://127.0.0.1:3100`. Nunca coloque a chave em HTML ou JavaScript do navegador. Adicione a autenticação da sua aplicação antes de usar este modelo fora da demonstração local.

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

A resposta lista os sites autorizados em `data`. Use um valor `data[].id` como `profile_id` nos pedidos seguintes. Se a lista estiver vazia, verifique os sites escolhidos e o seu acesso. Os exemplos cURL, JavaScript e Go também consultam a quota do primeiro perfil com `quotas:read`.

A **Referência da API pública** na navegação para programadores descreve caminhos, scopes e respostas.

## Autenticar-se

Crie uma chave pessoal em **Definições → Acesso à API** para os seus scripts ou autorize um cliente OAuth com PKCE S256. Limite perfis e scopes ao necessário. Envie o token como Bearer. Cookies do navegador e tokens Supabase não autorizam esta API.

Para uma aplicação que atua em nome do utilizador, comece em `https://app.sorank.com/.well-known/oauth-protected-resource/api/public/v1` e `https://app.sorank.com/.well-known/oauth-authorization-server`. Descubra os endpoints OAuth anunciados, registe um cliente público se necessário e use authorization code com PKCE S256, os scopes necessários e o recurso exato `https://app.sorank.com/api/public/v1`. Um token OAuth MCP não serve para REST.

## Permissões e quota

Consulte `getQuotas` antes de gerar. O exemplo requer `profiles:read`, `quotas:read`, `calendar:read`, `calendar:write`, `articles:read`, `articles:generate` e `articles:publish`. Criar um calendário também requer direitos de geração e publicação. A aplicação, REST e MCP partilham a quota; mais chaves não a aumentam.

Os scopes de leitura **Ver produtos** (`products:read`), **Ver análises** (`analytics:read`), **Ver visibilidade de IA** (`geo:read`) e **Ver configurações** (`settings:read`) nunca consomem quota. **Editar configurações** (`settings:write`) altera as mesmas definições que a aplicação; conceda-o apenas se uma integração as tiver de alterar.

## Verificar a elegibilidade

A API fica disponível depois de concluir o onboarding na Sorank e se o site tiver uma subscrição válida. Um colaborador de agência usa a subscrição do proprietário do site. A Sorank verifica-o ao criar uma chave ou aprovar uma ligação OAuth, e novamente em cada pedido.

* `listProfiles` devolve apenas os sites elegíveis.
* Um onboarding por concluir devolve `403 onboarding_required`.
* Um site sem subscrição válida devolve `402 subscription_required`, com `details.reason` (`paused`, `past_due`, `incomplete` ou `inactive`) quando conhecido.

Conclua o onboarding ou atualize a subscrição em **Definições → Faturação**. A sua chave existente volta a funcionar no pedido seguinte.

## Ler produtos, análises, visibilidade de IA e definições

Estas leituras devolvem o que a aplicação Sorank lhe mostra. Nunca iniciam uma análise, uma sincronização ou uma geração.

* **Produtos** (`products:read`): `listProducts` percorre o catálogo de produtos com um cursor. Após `409 product_catalog_cursor_stale`, recomece na primeira página.
* **Análises** (`analytics:read`): desempenho, pesquisas, páginas, países e dispositivos do Search Console, e tráfego do Google Analytics vindo de assistentes de IA. `start` e `end` (`YYYY-MM-DD`) são obrigatórios. Cada chamada lê o Google em tempo real e tem um limite de frequência mais baixo. Ligue primeiro o Google na Sorank; caso contrário recebe `409 gsc_not_connected` ou `409 ga_not_connected`, e `403 google_access_lost` se o acesso tiver sido perdido.
* **Visibilidade de IA** (`geo:read`): estatísticas GEO, evolução das menções, cronologia das citações, fontes que citam o site e concorrentes citados numa análise.
* **Definições** (`settings:read`): `getSettings` devolve o site, o brief de negócio com a sua `revision`, os concorrentes, as predefinições dos artigos, os visuais e o canal do YouTube.

## Alterar definições

Com `settings:write`, pode alterar o nome da empresa, a cor da marca e o mercado, substituir o brief de negócio e a lista de concorrentes, alterar as predefinições dos artigos do calendário, o modo de imagem e as capas, e ligar, pausar ou desligar o canal do YouTube. O URL do site não pode ser alterado através da API (`400 field_not_writable`).

Cada alteração requer um `Idempotency-Key`. A resposta contém as definições resultantes, um recibo e uma lista `effects` com os processos iniciados em segundo plano, como na aplicação: `geo_questions_regeneration_queued`, `backlink_refresh_queued`, `calendar_slots_shifted`, `image_gallery_analysis_queued`, `youtube_sync_queued` ou `youtube_purge_queued`.

Para substituir o brief de negócio, envie a `expected_revision` lida em `getSettings`. Se o brief tiver mudado entretanto, recebe `409 business_brief_revision_conflict`: volte a ler as definições antes de repetir.

## Experimentar um fluxo REST completo

Este exemplo avançado cria conteúdo e pode consumir quota. Substitua os IDs, datas futuras, chaves de idempotência e credencial de exemplo antes de o executar. As datas do calendário usam o fuso IANA indicado. Consulte `getCapabilities` antes de escolher destino ou categoria CMS.

```sh theme={null}
# Substitua os UUID de exemplo pelos da sua conta.
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"
```

A referência da API pública descreve operações, campos, respostas e erros.

## Acompanhar e repetir

Cada alteração requer um `Idempotency-Key` único. Reutilize a mesma chave ao repetir o mesmo comando. Leia `data.receipt.operation_id` e consulte `getOperation` até `succeeded`, `failed` ou `verification_pending`. Um recibo aceite não confirma a publicação. Um lote de geração de artigos devolve `202` com um recibo já `succeeded`; acompanhe cada artigo indicado em `selection.accepted_ids` com `getArticle`. Os direitos são verificados em cada consulta; a revogação tem efeito imediato.

## Tratar erros

Todos os erros têm a mesma forma em REST e MCP: `{"error": {"code", "message", "request_id", "retryable", "details"}}`. Baseie a sua lógica em `code`, não na mensagem. Repita apenas quando `retryable` for `true`, após `Retry-After` quando presente.

* `401 invalid_credential`: credencial inválida, expirada ou revogada.
* `403 missing_scope` ou `403 onboarding_required`; `402 subscription_required` ou `402 insufficient_quota`.
* `404 not_found`: o recurso não existe ou não é acessível com esta credencial.
* `409`: um conflito como `idempotency_conflict`; `429 rate_limited`: aguarde `Retry-After`.
* `503 auth_state_unavailable`: o acesso é recusado até o estado de autorização recuperar; tente mais tarde.

A referência da API pública indica os códigos de cada operação. Partilhe `code` e `request_id` com o suporte, nunca o token.

Para ligar um assistente, consulte o guia MCP.


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