SeoPrimo · SEO per Google e per le AI · presenza garantita, o rimborso della quota di attivazione · Audit gratuito del tuo sito
SeoPrimo · SEO per Google e per le AI · presenza garantita, o rimborso della quota di attivazione · Audit gratuito del tuo sito
seoprimo
— Sviluppatori · API REST

API REST.

Gestisci siti, audit, parole chiave, posizioni, backlink, Score e report dal tuo codice. Un'unica chiave, JSON ovunque, errori sempre nello stesso formato e il prezzo di ogni chiamata prima di spenderlo.

26
operazioni, OpenAPI 3.1
60/min
per chiave
dry_run
il prezzo prima di spendere
— Autenticazione

Indirizzo e chiave.

L'indirizzo di base è https://seoprimo.it/api/v1. Ogni richiesta porta la chiave nell'intestazione Authorization: Bearer sp_live_…. I cookie del pannello non funzionano qui, e la chiave non funziona nel pannello.

curl
curl -sS https://seoprimo.it/api/v1/sites \
  -H "Authorization: Bearer sp_live_..."

Crea le chiavi dal pannello, in Integrazioni, Chiavi API: il segreto si vede una sola volta (formato sp_live_ + 43 caratteri, noi teniamo solo l'hash) e si può revocare in qualunque momento. Gli scope sono due:

Scope delle chiavi API
ScopePermette
readTutti i GET: elenchi, dettagli, Score, report, crediti, esito delle run.
writePOST e DELETE: aggiungere o eliminare un sito, avviare audit e analisi a crediti, creare monitoraggi.

Le due cose sono indipendenti: una chiave solo write non può leggere. Per l'uso normale scegli entrambi.

— Limiti

Richieste al minuto.

Ogni chiave può fare 60 richieste al minuto (finestra di 60 secondi che parte dalla prima richiesta). Il limite è lo stesso per l'API REST e per il server MCP: i due canali si dividono lo stesso conteggio.

Ogni risposta riporta X-RateLimit-Limit, il limite della chiave. Oltre il limite la risposta è 429 (rate_limited) e l'intestazione Retry-After dice fra quanti secondi riprovare: non serve altro che aspettare.

Per aspettare il risultato di un lavoro lungo non interrogare di continuo: una richiesta ogni 5-10 secondi basta, oppure usa i webhook.

— Crediti

Il prezzo prima di spenderlo.

Le chiamate a pagamento usano i crediti dell'attività, gli stessi del pannello (prima quelli del piano, poi i pacchetti, ciascuno dalla scadenza più vicina). Sono gratis: elenco e dettaglio dei siti, aggiunta di un sito, lettura di audit, Score, report, crediti e run, creazione di un monitoraggio. L'audit rapido costa 10 crediti fissi; l'audit completo costa la scansione, 2 crediti fino a 200 pagine e 1 credito ogni 100 pagine oltre, più i controlli Lighthouse se li chiedi; le analisi del motore (parole chiave, posizioni, backlink, concorrenti, risultati di ricerca, SEO locale, visibilità AI) costano in base a quanti dati chiedi.

dry_run: la stima senza esecuzione

Aggiungi ?dry_run=true a qualunque POST: non parte niente, non si addebita niente e la risposta è la stima.

curl
curl -sS -X POST "https://seoprimo.it/api/v1/sites/<site_id>/keywords/research?dry_run=true" \
  -H "Authorization: Bearer $SEOPRIMO_KEY" -H "Content-Type: application/json" \
  -d '{"seeds": ["scarpe running"], "result_limit": 150}'
# {"estimated_credits": 12}
  • La stima è sempre il caso peggiore: è la cifra che viene prenotata prima della chiamata.
  • A chiamata finita il consumo si ricalcola sui dati davvero ricevuti e non supera mai la stima: la differenza torna disponibile.
  • Se la chiamata fallisce, i crediti tornano per intero.
  • L'intestazione X-Credits-Charged dice quanti crediti ha addebitato la chiamata (per un lavoro lungo in corso, quelli prenotati).
  • Senza crediti sufficienti la risposta è 402 e non parte niente:
JSON
{
  "error": "insufficient_credits",
  "needed": 25,
  "available": 4
}
— Lavori lunghi

Avvia, poi controlla.

Alcune operazioni non finiscono subito: l'audit completo e il controllo delle posizioni rispondono 202 con una run in corso. Leggi l'esito da GET /runs/{id}: lo stato è queued, running, done o failed, e il risultato (result) c'è solo a done. Se la run fallisce, i crediti tornano indietro.

curl
# a long job (full audit, rank check) answers 202 with a run
RUN_ID=$(curl -sS -X POST "$API/sites/$SITE_ID/full-audits" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"max_pages": 100, "run_lighthouse": false}' | jq -r .id)

# poll the run: status is queued, running, done or failed; "result" is filled only when done
curl -sS "$API/runs/$RUN_ID" -H "$AUTH" | jq '{status, credits_reserved, credits_charged, result}'

L'audit rapido è diverso: non è una run. Risponde 202 con l'id dell'audit e si segue con GET /sites/{id}/audits, cercando quell'id. È il percorso dell'esempio qui sotto.

— Esempio

Sito, audit rapido, Score.

Il percorso completo: aggiungi un sito, chiedi il prezzo dell'audit rapido, avvialo, aspetta che finisca e leggi lo SeoPrimo Score. Servono i crediti per l'audit (10) e una chiave con entrambi gli scope. Lo Score dell'audit rapido è preliminare (preliminary: true) finché il pilastro Autorità non è misurato.

curl
export SEOPRIMO_KEY="sp_live_..."
API=https://seoprimo.it/api/v1
AUTH="Authorization: Bearer $SEOPRIMO_KEY"

# 1. add the site (free). If it already exists the answer is 409: take its id from GET /sites
SITE_ID=$(curl -sS -X POST "$API/sites" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"url": "https://www.acmescarpe.it/", "name": "Acme Scarpe"}' | jq -r .id)

# 2. the price of the quick audit, without running it
curl -sS -X POST "$API/sites/$SITE_ID/audits?dry_run=true" -H "$AUTH"
# {"estimated_credits":10}

# 3. start the quick audit (answer 202: {"id": "...", "status": "queued", "cost": 10})
AUDIT_ID=$(curl -sS -X POST "$API/sites/$SITE_ID/audits" -H "$AUTH" | jq -r .id)

# 4. poll until the audit is done or failed
while true; do
  STATUS=$(curl -sS "$API/sites/$SITE_ID/audits" -H "$AUTH" \
    | jq -r --arg id "$AUDIT_ID" '.audits[] | select(.id == $id) | .status')
  echo "status: $STATUS"
  [ "$STATUS" = "done" ] || [ "$STATUS" = "failed" ] && break
  sleep 10
done

# 5. the SeoPrimo Score
curl -sS "$API/sites/$SITE_ID/score" -H "$AUTH" | jq '{score, preliminary, pillars}'
Python
import os
import time

import httpx

api = httpx.Client(
    base_url="https://seoprimo.it/api/v1",
    headers={"Authorization": f"Bearer {os.environ['SEOPRIMO_KEY']}"},
    timeout=30,
)


def ok(response: httpx.Response) -> dict:
    if response.is_error:
        body = response.json()  # always {"error": code, "detail": ...}
        raise RuntimeError(f"{response.status_code} {body['error']}: {body['detail']}")
    return response.json()


# 1. add the site (free). If it already exists the answer is 409: take its id from GET /sites
site = ok(api.post("/sites", json={"url": "https://www.acmescarpe.it/", "name": "Acme Scarpe"}))
site_id = site["id"]

# 2. the price of the quick audit, without running it
print(ok(api.post(f"/sites/{site_id}/audits", params={"dry_run": "true"})))  # {'estimated_credits': 10}

# 3. start the quick audit (answer 202)
audit_id = ok(api.post(f"/sites/{site_id}/audits"))["id"]

# 4. poll until the audit is done or failed
while True:
    audits = ok(api.get(f"/sites/{site_id}/audits"))["audits"]
    audit = next(a for a in audits if a["id"] == audit_id)
    print("status:", audit["status"])
    if audit["status"] in ("done", "failed"):
        break
    time.sleep(10)

# 5. the SeoPrimo Score
score = ok(api.get(f"/sites/{site_id}/score"))
print(score["score"], "preliminary" if score["preliminary"] else "full", score["pillars"])
JavaScript
// Node 18+ (global fetch)
const API = "https://seoprimo.it/api/v1";
const headers = { Authorization: `Bearer ${process.env.SEOPRIMO_KEY}`, "Content-Type": "application/json" };
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function call(method, path, body) {
  const response = await fetch(API + path, { method, headers, body: body && JSON.stringify(body) });
  const data = await response.json();
  if (!response.ok) throw new Error(`${response.status} ${data.error}: ${JSON.stringify(data.detail)}`);
  return data;
}

// 1. add the site (free). If it already exists the answer is 409: take its id from GET /sites
const site = await call("POST", "/sites", { url: "https://www.acmescarpe.it/", name: "Acme Scarpe" });

// 2. the price of the quick audit, without running it
console.log(await call("POST", `/sites/${site.id}/audits?dry_run=true`)); // { estimated_credits: 10 }

// 3. start the quick audit (answer 202)
const started = await call("POST", `/sites/${site.id}/audits`);

// 4. poll until the audit is done or failed
let audit;
do {
  await sleep(10_000);
  const { audits } = await call("GET", `/sites/${site.id}/audits`);
  audit = audits.find((a) => a.id === started.id);
  console.log("status:", audit.status);
} while (!["done", "failed"].includes(audit.status));

// 5. the SeoPrimo Score
const score = await call("GET", `/sites/${site.id}/score`);
console.log(score.score, score.preliminary ? "preliminary" : "full", score.pillars);

La risposta di GET /sites/{id}/score ha la forma {"score": 72, "preliminary": true, "pillars": {…}, …}: score è lo SeoPrimo Score (0-100), pillars i cinque pilastri, checks i controlli superati o no, history le ultime 12 misure.

— Errori

Sempre lo stesso formato.

Ogni errore è un JSON {"error": codice, "detail": testo}: error è un codice stabile su cui puoi basare il codice, detail un testo per le persone (in italiano; per validation_error è l'elenco dei campi sbagliati). L'unica variante è il 402, che al posto di detail porta needed e available.

Errori dell'API
StatoCodiceSignificato
401invalid_api_keyChiave mancante, non valida o revocata.
402insufficient_creditsCrediti insufficienti. Il corpo aggiunge needed e available.
403insufficient_scopeLa chiave non ha lo scope richiesto (read per i GET, write per POST e DELETE).
404not_foundLa risorsa non esiste o è di un'altra attività.
409conflictIl sito è già stato aggiunto.
422validation_errorRichiesta non valida: detail è l'elenco dei campi sbagliati (loc, msg, type).
429rate_limitedPiù di 60 richieste al minuto con questa chiave. Aspetta i secondi indicati da Retry-After.
500internal_errorErrore interno: riprova più tardi.
502engine_errorIl motore di analisi ha dato errore. I crediti sono restituiti.
503engine_not_configuredIl motore di analisi non è configurato o non risponde (engine_not_configured, engine_unavailable, service_unavailable). I crediti sono restituiti.
— Riferimento

Tutti gli endpoint.

La tabella è generata dalla specifica OpenAPI al momento della pubblicazione di questa pagina: non può restare indietro. Le * sono i campi obbligatori; i percorsi con {id} portano un id nell'indirizzo. Schemi, esempi di risposta e «Try it out» sono nella documentazione interattiva.

Endpoint dell'API REST v1
ChiamataCosa faParametriScopeCosto
GET /sitesElenco dei siti-readGratis
POST /sitesAggiungi un sitourl*, name, dry_runwriteGratis
GET /sites/{site_id}Dettaglio sito-readGratis
DELETE /sites/{site_id}Elimina un sito-writeGratis
POST /sites/{site_id}/auditsAvvia l'audit rapidodry_runwrite10 crediti fissi
GET /sites/{site_id}/auditsAudit rapidi del sito-readGratis
POST /sites/{site_id}/full-auditsAvvia l'audit completomax_pages, run_lighthouse, dry_runwriteA crediti · lavoro lungo
POST /sites/{site_id}/keywords/researchRicerca di parole chiaveseeds*, result_limit, dry_runwriteA crediti
POST /sites/{site_id}/keywords/metricsMetriche di parole chiavekeywords*, dry_runwriteA crediti
GET /sites/{site_id}/rankings/trackersMonitoraggi posizioni-readGratis
POST /sites/{site_id}/rankings/trackersCrea il monitoraggiokeywords*, schedule, dry_runwriteGratis
POST /sites/{site_id}/rankings/trackers/{tracker_id}/runControlla le posizioni oradry_runwriteA crediti · lavoro lungo
POST /sites/{site_id}/backlinks/overviewPanoramica backlinktarget, dry_runwriteA crediti
POST /sites/{site_id}/backlinks/profileProfilo backlinktarget, page, page_size, dry_runwriteA crediti
POST /sites/{site_id}/competitorsConcorrenti nelle ricerchekeywords*, limit, dry_runwriteA crediti
POST /sites/{site_id}/serpRisultati di ricercakeywords*, depth, dry_runwriteA crediti
POST /sites/{site_id}/local/profileScheda Google Businessbusiness_name, dry_runwriteA crediti
POST /sites/{site_id}/local/reviewsRecensioni Googlebusiness_name, dry_runwriteA crediti
POST /sites/{site_id}/ai-visibilityVisibilità nelle risposte AIbrand, prompts, keywords, platforms, limit, dry_runwriteA crediti
GET /sites/{site_id}/scoreSeoPrimo Score del sito-readGratis
GET /sites/{site_id}/reportsReport del sito-readGratis
GET /sites/{site_id}/reports/{report_id}Un report con il suo HTML-readGratis
POST /sites/{site_id}/agentsAvvia un agenteagent*, budget_credits, dry_runwriteA crediti · lavoro lungo
GET /sites/{site_id}/agents/{run_id}Stato di un agente-readGratis
GET /creditsCrediti disponibili-readGratis
GET /runs/{run_id}Stato e risultato di una run-readGratis
Pronto a provare

Una chiave,
venti endpoint.

Crea la chiave dal pannello e fai la prima chiamata in un minuto.

Cosa trovi nella guida
curl
Esempi completi
con Python e JavaScript
402
Crediti sotto controllo
dry_run su ogni POST
MCP
E per gli agenti
stessa chiave, 11 tool