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

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`) );