API Reference

PerSeo Insights REST API v1.0

HTTPS only JSON v1.0.0

Base URL

https://insights.perseodesign.com/api/v1

L'API REST di PerSeo Insights permette di avviare scansioni SEO, leggere i risultati, esportare report e gestire i token di accesso in modo programmatico. Ideale per CI/CD, monitoring automatizzato e integrazioni con tool di terze parti. La v1.0.0 aggiunge punteggi di leggibilità, analisi keyword, scansione bulk (fino a 50 URL), webhook callback e auto-fallback HTTPS/HTTP.

Per creare la tua prima API key accedi alla Dashboard, sezione API & Token.

Autenticazione

Tutti gli endpoint richiedono un Bearer token nell'header HTTP:

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

API Key (permanente)

Token a lunga vita creati dalla Dashboard. Iniziano con sk_live_. Usali per script, automazioni e integrazioni server-side.

JWT (sessione 24h)

Token a breve vita emessi da POST /auth/login. Usali per client interattivi che richiedono login esplicito.

Scopes disponibili

ScopeDescrizione
readAccesso in lettura: profilo, history, lista token
scanAvvia nuove scansioni (POST /scans)
exportEsporta risultati (GET /scans/{id}/export/{format})

Rate limits

I limiti API sono allineati ai limiti del sito web. Il reset avviene ogni 24h (finestra mobile).

Tier Single Scan/giorno Sitemap Scan/giorno Crawl/giorno Report schedulati
Guest (non autenticato)11--
Free (registrato)5511
Pro50101010

Per maggiori dettagli sui piani e le funzionalità, visita la pagina dei Prezzi.

Response format

Tutte le risposte seguono il formato standard JSON:

Successo

{ "success": true, "data": { ... }, "meta": { "timestamp": "2026-02-25T10:30:00Z", "version": "1.0", "request_id": "550e8400-..." } }

Errore

{ "success": false, "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Scan limit reached (5/5).", "retry_after": 14400 }, "meta": { ... } }

Auth

POST /api/v1/auth/login No auth

Autentica con email/password e riceve un JWT access token (24h).

# Request curl -s -X POST https://insights.perseodesign.com/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"[email protected]","password":"password123"}'
Response 200
{ "success": true, "data": { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_in": 86400, "token_type": "Bearer", "user": { "id": 42, "username": "mario.rossi", "email": "[email protected]" } } }
GET /api/v1/auth/verify JWT Bearer

Verifica la validita' di un JWT token.

Token management

GET /api/v1/tokens scope: read

Lista tutte le API key dell'utente autenticato.

curl https://insights.perseodesign.com/api/v1/tokens \ -H "Authorization: Bearer sk_live_xxxx"
POST /api/v1/tokens scope: read

Crea una nuova API key. Il token viene mostrato una sola volta.

CampoTipoReq.Descrizione
namestringSIEtichetta del token (max 100 char)
scopesarrayNODefault ["read"]. Valori: read, scan, export
curl -X POST https://insights.perseodesign.com/api/v1/tokens \ -H "Authorization: Bearer sk_live_xxxx" \ -H "Content-Type: application/json" \ -d '{"name":"Script CI","scopes":["read","scan","export"]}'
IMPORTANTE: Il campo token nella risposta viene mostrato una sola volta. Salvalo subito in modo sicuro.
DELETE /api/v1/tokens/{id} scope: read

Revoca (disattiva) permanentemente un'API key.

Scansioni

POST /api/v1/scans scope: scan

Avvia una scansione SEO asincrona. Risponde con 202 Accepted e un scan_id da usare per il polling.

CampoTipoReq.Descrizione
urlstringSIURL o dominio senza protocollo (es. example.com)
modestringNOsingle (default) o sitemap
bypass_cacheboolNOForza analisi fresh ignorando la cache 60 min (default false)
webhook_urlstringNOURL HTTPS su cui fare POST al completamento (body JSON con scan_id, status, result)
curl -X POST https://insights.perseodesign.com/api/v1/scans \ -H "Authorization: Bearer sk_live_xxxx" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","mode":"single"}'
Response 202 Accepted
{ "success": true, "data": { "scan_id": "a7f3d2e1-8b4c-4f9a-b6d5-c3e2f1a0b9d8", "status": "queued", "mode": "single", "url": "https://example.com", "poll_url": "/api/v1/scans/a7f3d2e1-..." } }
GET /api/v1/scans/{scan_id} scope: read

Recupera stato e risultati di una scansione. Fai polling ogni 2-3 secondi finche' status != "running".

running

In corso

complete

Completata

error

Fallita

stopped

Interrotta

# Polling loop bash while true; do RESULT=$(curl -s \ -H "Authorization: Bearer $TOKEN" \ "https://insights.perseodesign.com/api/v1/scans/$SCAN_ID") STATUS=$(echo $RESULT | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['status'])") [ "$STATUS" != "running" ] && break sleep 3 done

AI Overview

Recupera l'analisi AI Overview (readiness SGE) per una scansione singolo URL gia' salvata. Il punteggio misura quanto una pagina e' ottimizzata per apparire negli AI Overview di Google, in base a dati strutturati, profondita' dei contenuti, formato degli heading e altro.

GET /api/v1/scans/{scan_id}/ai-overview scope: read

Restituisce score AI Overview, grade (A-F), dettaglio dei controlli e segnali strutturati per la scansione indicata. Disponibile solo per scansioni singolo URL. Se la scansione e' precedente alla feature, ri-scansiona l'URL per generare i dati.

Parametri path

scan_id ID numerico della scansione da /api/v1/history o dal campo scan_id_db restituito a scansione completata.

Campi risposta

CampoDescrizione
ai_overview.scoreScore di readiness 0-100
ai_overview.gradeVoto letterale da A+ a F
ai_overview.checksArray di controlli: label, status (pass/fail/partial), score, max_score, detail
ai_overview.signalsFlag booleani: has_faq_schema, has_speakable, has_howto, has_article_schema, question_heading_count
curl -s \ -H "Authorization: Bearer $TOKEN" \ "https://insights.perseodesign.com/api/v1/scans/123/ai-overview"

Risposta di esempio

{ "success": true, "data": { "scan_id": 123, "url": "https://example.com", "scanned_at": "2026-05-07T08:30:52", "ai_overview": { "score": 24, "grade": "F", "checks": [ { "label": "FAQ Schema", "label_it": "Schema FAQ", "status": "fail", "score": 0, "max_score": 20, "detail": "No FAQPage schema found.", "detail_it": "Nessuno schema FAQPage trovato." } ], "signals": { "has_faq_schema": false, "has_speakable": false, "has_howto": false, "has_article_schema": false, "question_heading_count": 0, "question_headings": [] } } } }

Codici errore

404 NOT_FOUNDScansione non trovata o di un altro utente
422 NO_DATAScansione precedente alla feature AI Overview: ri-scansiona per generare i dati

Scansione bulk

POST /api/v1/scans/bulk scope: scan

Avvia scansioni parallele per più URL (fino a 50). Restituisce un batch_id per monitorare il progresso.

CampoTipoReq.Descrizione
urlsarraySIArray di URL/domini da scansionare (max 50)
modestringNOsingle (default)
bypass_cacheboolNOForza analisi fresh per tutti gli URL
curl -X POST https://insights.perseodesign.com/api/v1/scans/bulk \ -H "Authorization: Bearer sk_live_xxxx" \ -H "Content-Type: application/json" \ -d '{"urls":["https://example.com","https://myblog.com","example.org"]}'
Response 202 Accepted
{ "success": true, "data": { "batch_id": "b1c2d3e4-f5a6-7890-abcd-ef1234567890", "total": 3, "status": "running", "poll_url": "/api/v1/scans/bulk/b1c2d3e4-..." } }
GET /api/v1/scans/bulk/{batch_id} scope: read

Monitora lo stato di un batch bulk. Restituisce risultati parziali man mano che le scansioni completano.

curl https://insights.perseodesign.com/api/v1/scans/bulk/b1c2d3e4-... \ -H "Authorization: Bearer sk_live_xxxx"
Response (in corso)
{ "success": true, "data": { "batch_id": "b1c2d3e4-...", "status": "running", "total": 3, "completed": 1, "results": [ { "url": "https://example.com", "status": "complete", "result": { /* PageAnalysis object */ } }, { "url": "https://myblog.com", "status": "running" }, { "url": "https://example.org", "status": "running" } ] } }

Storico

GET /api/v1/history scope: read

Storico delle scansioni dell'utente con paginazione.

ParametroTipoDefaultDescrizione
pageint1Numero pagina
per_pageint20Risultati per pagina (max 50)
curl "https://insights.perseodesign.com/api/v1/history?page=1&per_page=20" \ -H "Authorization: Bearer sk_live_xxxx"

Esportazione

GET /api/v1/scans/{id}/export/{format} scope: export

Esporta i risultati di una scansione. Formati supportati: json, csv, pdf.

# Scarica CSV curl -H "Authorization: Bearer sk_live_xxxx" \ "https://insights.perseodesign.com/api/v1/scans/127/export/csv" \ -o report.csv

Crawl

Avvia e monitora crawl completi di un sito. I risultati vengono restituiti in modo asincrono tramite polling.

POST /api/v1/crawls scope: scan

Avvia un crawl asincrono. Restituisce subito un task_id da usare per il polling.

CampoTipoObbligatorioDescrizione
domainstringsiDominio o URL da crawlare
max_pagesintnoPagine massime (limite piano: Free=1000, Pro=10000)
max_depthintnoProfondita' massima (limite piano: Free=4, Pro=8)
curl -X POST https://insights.perseodesign.com/api/v1/crawls \ -H "Authorization: Bearer sk_live_xxxx" \ -H "Content-Type: application/json" \ -d '{"domain": "https://example.com", "max_pages": 500}'
Response 202
{ "success": true, "data": { "task_id": "a1b2c3d4-...", "status": "running", "domain": "https://example.com", "max_pages": 500, "max_depth": 4, "poll_url": "/api/v1/crawls/a1b2c3d4-..." } }
GET /api/v1/crawls/{task_id} scope: read

Polling status del crawl. Lo stato puo' essere running, complete, error o stopped. Al completamento include job_id e report_url.

curl https://insights.perseodesign.com/api/v1/crawls/a1b2c3d4-... \ -H "Authorization: Bearer sk_live_xxxx"
Response (complete)
{ "success": true, "data": { "task_id": "a1b2c3d4-...", "status": "complete", "domain": "https://example.com", "job_id": 42, "report_url": "/crawl/report/42", "crawled": 312, "summary": { "total_pages": 312, "total_issues": 47, "broken_pages": 3, "orphan_pages": 12 } } }
GET /api/v1/crawls scope: read

Lista i crawl dell'utente autenticato (max 100).

ParametroTipoDefaultDescrizione
limitint20Risultati massimi (max 100)
curl "https://insights.perseodesign.com/api/v1/crawls?limit=10" \ -H "Authorization: Bearer sk_live_xxxx"

Report schedulati

Crea e gestisci report SEO automatici inviati via email con cadenza settimanale o mensile.

GET /api/v1/scheduled-reports scope: read

Elenca tutti i report schedulati dell'utente autenticato.

POST /api/v1/scheduled-reports scope: write

Crea un nuovo report schedulato.

CampoTypeObbligatorioDescrizione
urlstringsiURL della pagina da scansionare
schedule_typestringsiweekly o monthly
day_of_weekintse weekly0 (Lun) a 6 (Dom)
day_of_monthintse monthly1 a 28
labelstringnoEtichetta opzionale
notify_emailstringnoEmail di notifica (default: email account)
PUT /api/v1/scheduled-reports/{id} scope: write

Aggiorna un report schedulato. Accetta gli stessi campi del POST.

PATCH /api/v1/scheduled-reports/{id}/toggle scope: write

Attiva o disattiva un report schedulato (is_active).

DELETE /api/v1/scheduled-reports/{id} scope: write

Elimina definitivamente un report schedulato.

Utente

GET /api/v1/user/profile scope: read

Restituisce il profilo dell'utente autenticato.

GET /api/v1/user/usage scope: read

Statistiche di utilizzo: scan oggi, richieste API, limiti del piano.

Codici di errore

CodiceHTTPDescrizione
MISSING_AUTH 401 Header Authorization assente o malformato
INVALID_TOKEN 401 Token non valido, scaduto o revocato
INSUFFICIENT_SCOPE 403 Il token non ha lo scope richiesto
INVALID_CREDENTIALS 401 Email/password errate
ACCOUNT_DISABLED 403 Account disabilitato
RATE_LIMIT_EXCEEDED 429 Limite scan o richieste superato
INVALID_REQUEST 400 Parametri mancanti o malformati
INVALID_URL 400 URL non valido
INVALID_SCOPES 400 Scope non riconosciuti
NOT_FOUND 404 Risorsa non trovata
FORBIDDEN 403 Accesso negato (risorsa di un altro utente)
INTERNAL_ERROR 500 Errore interno del server

Esempi d'uso

Flusso completo: login + crea token + prima scansione (Bash)

# 1. Login e ottieni JWT JWT=$(curl -s -X POST https://insights.perseodesign.com/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"[email protected]","password":"password123"}' \ | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['access_token'])") # 2. Crea una API key permanente TOKEN=$(curl -s -X POST https://insights.perseodesign.com/api/v1/tokens \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{"name":"Script CI","scopes":["read","scan","export"]}' \ | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['token'])") echo "Salva questo token: $TOKEN" # 3. Avvia una scansione SCAN_ID=$(curl -s -X POST https://insights.perseodesign.com/api/v1/scans \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","mode":"single"}' \ | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['scan_id'])") # 4. Polling finche' completo while true; do RESULT=$(curl -s -H "Authorization: Bearer $TOKEN" \ "https://insights.perseodesign.com/api/v1/scans/$SCAN_ID") STATUS=$(echo $RESULT | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['status'])") echo "Status: $STATUS" [ "$STATUS" != "running" ] && break sleep 3 done # 5. Esporta in CSV curl -s -H "Authorization: Bearer $TOKEN" \ "https://insights.perseodesign.com/api/v1/scans/127/export/csv" \ -o "report_$(date +%Y%m%d).csv"

Python: monitoring di una lista di siti

import time import requests API_BASE = "https://insights.perseodesign.com/api/v1" TOKEN = "sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" HEADERS = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"} SITES = ["https://example.com", "https://myblog.com"] def scan_site(url): resp = requests.post(f"{API_BASE}/scans", json={"url": url, "mode": "single"}, headers=HEADERS) resp.raise_for_status() scan_id = resp.json()["data"]["scan_id"] while True: time.sleep(3) data = requests.get(f"{API_BASE}/scans/{scan_id}", headers=HEADERS).json()["data"] if data["status"] == "complete": return data.get("result", {}) elif data["status"] in ("error", "stopped"): raise RuntimeError(data.get("error")) for site in SITES: result = scan_site(site) g = result.get("googlebot", {}) print(f"{site}: SEO={g.get('seo_score')} Errori={len(g.get('errors',[]))}")

Python: avvia un crawl completo e attendi i risultati

import time import requests API_BASE = "https://insights.perseodesign.com/api/v1" TOKEN = "sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" HEADERS = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"} # 1. Avvia il crawl resp = requests.post( f"{API_BASE}/crawls", json={"domain": "https://example.com", "max_pages": 500}, headers=HEADERS, ) resp.raise_for_status() task_id = resp.json()["data"]["task_id"] print(f"Crawl avviato: {task_id}") # 2. Polling finche' completo while True: time.sleep(5) data = requests.get( f"{API_BASE}/crawls/{task_id}", headers=HEADERS ).json()["data"] status = data["status"] progress = data.get("progress", {}) crawled = progress.get("crawled", 0) print(f"Stato: {status} ({crawled} pagine)") if status == "complete": summary = data.get("summary", {}) print(f"Completato! {summary.get('total_pages')} pagine, {summary.get('total_issues')} issue") print(f"Report: https://insights.perseodesign.com{data['report_url']}") break elif status in ("error", "stopped"): raise RuntimeError(f"Crawl terminato con stato: {status}")

JavaScript/Node.js: scansione sitemap

const fetch = require('node-fetch'); const API_BASE = 'https://insights.perseodesign.com/api/v1'; const TOKEN = 'sk_live_xxxx'; const headers = { 'Authorization': `Bearer ${TOKEN}`, 'Content-Type': 'application/json' }; async function scanSitemap(url) { const { data } = await (await fetch(`${API_BASE}/scans`, { method: 'POST', headers, body: JSON.stringify({ url, mode: 'sitemap' }) })).json(); let result; while (true) { await new Promise(r => setTimeout(r, 3000)); result = (await (await fetch(`${API_BASE}/scans/${data.scan_id}`, { headers })).json()).data; if (result.status !== 'running') break; } return result; } scanSitemap('https://myblog.com/sitemap.xml').then(r => console.log(`Completata! ${r.result?.length} URL analizzati`) );