AIREITER

API GraphQL senza query nella richiesta: due strade per recuperare un’operazione persistita

Ultimo Aggiornamento: 2026-07-31 06:36:10

Apri il pannello Network di DevTools mentre una pagina basata su GraphQL completa il caricamento. Cerchi nei body delle richieste la solita query { ... }, ma non compare da nessuna parte: trovi soltanto un operationName, un hash di sessantaquattro caratteri e un insieme di variables.

Non ti è sfuggito nulla. Hai davanti un’operazione persistita: il client non invia più la query in chiaro, ma solo un hash preregistrato. Il server usa quell’hash per recuperare la query dal proprio registro, la ricostruisce e la esegue. Da qui in poi, l’analisi del traffico non basta più: puoi sapere quale operazione è stata chiamata e con quali variabili, ma non quali campi seleziona né quale struttura avrà la risposta.

La reazione più comune è anche quella sbagliata: pensare di dover “craccare” l’hash. Un hash è a senso unico, non si cracca, e soprattutto non serve farlo. Il problema reale è capire in quale scenario ti trovi, per poi scegliere il metodo giusto. Tra le due strade c’è un ordine di grandezza di differenza nei costi: imboccare quella sbagliata significa buttare tempo e lavoro.

Perché la query non compare nella cattura

Prima di parlare di recupero, vale la pena capire perché esiste questo meccanismo. Senza questo passaggio è difficile valutare la strategia giusta.

Con GraphQL in chiaro il limite è evidente: le query possono essere lunghe, inviare l’intero albero dei campi a ogni richiesta è inefficiente e il server deve accettare query arbitrarie, esponendo tutta la superficie dello schema. Le operazioni persistite risolvono entrambi i problemi. In fase di build, tutte le query usate dal client vengono estratte, sottoposte a hash e registrate sul server come whitelist. A runtime il client invia soltanto hash e variabili; il server accetta esclusivamente hash registrati e rifiuta tutto il resto. È una scelta architetturale concreta per prestazioni e controllo, non una misura anti-scraping progettata apposta. La documentazione di Apollo Automatic Persisted Queries la presenta come pratica consigliata, usando lo SHA-256 della query al posto del testo in chiaro. Il fatto che la query non sia visibile nella cattura è semplicemente una conseguenza.

Per chi fa reverse engineering, la conseguenza è precisa: dalla richiesta è sparito il “cosa recuperare”. Restano tre elementi: un identificatore dell’operazione, cioè un hash o un operationName leggibile; le variabili; la risposta. Lo strato intermedio, quello che definisce i campi selezionati, non viaggia sulla rete.

Nella pratica si incontrano entrambi gli estremi. Da una parte ci sono piattaforme che non hanno mai adottato operazioni persistite e continuano a inviare la query in chiaro nel body. Dall’altra, richieste ridotte a involucri opachi in cui non si legge nemmeno un nome di campo. I due casi richiedono approcci diversi.

Due approcci, costi distanti un ordine di grandezza

Il primo approccio consiste nel trovare il testo della query o una tabella di mappatura dentro il build del client. Il secondo rinuncia del tutto a cercare la query: tratta l’operazione come una black box e la replica fedelmente.

Il primo sembra più completo, perciò molti partono da lì in automatico. È esattamente così che si inizia a sprecare lavoro. Conviene solo quando il testo della query è stato davvero distribuito al client, e non è affatto sempre così.

Metodo 1: cercare query e mappature nel build client

Il caso meno costoso è quello in cui la query non è mai stata nascosta.

La classifica delle tendenze di una piattaforma cinese di short video funziona così: un solo endpoint /graphql, body standard con la terna {operationName, variables, query}, campo query contenente l’intera query GraphQL in chiaro e un operationName leggibile, come hotRankQuery. Non c’è nulla da recuperare: una sola cattura mostra tutto. Non usa nemmeno operazioni persistite ed è l’estremo più semplice dello spettro.

Un po’ più impegnativo è il caso in cui le operazioni persistite esistono davvero, ma il client conserva comunque la mappatura. Per inviare un hash, il client deve sapere a quale operazione corrisponde; la tabella operationName-hash, talvolta accompagnata dalla query in chiaro, è spesso inclusa nel bundle frontend. Gli strumenti di build possono generarla come manifest oppure incorporarla in un modulo. Se la trovi, ottieni insieme query e hash, e puoi poi aggiungere campi o modificare il selection set.

La difficoltà non è cercare in sé, ma farlo in un build di decine di migliaia di righe, minificato e offuscato, dove la mappatura può essere spezzata e inline. È qui che un modello può fare davvero la differenza: non serve ragionamento, ma retrieval a blocchi.

Il criterio per il metodo 1 è semplice: se la query in chiaro o la sua mappatura sono presenti nel client, dedica prima dieci minuti a trovarle. Se ci riesci, è l’approccio più solido e ti dà il pieno controllo dell’endpoint.

Metodo 2: non inseguire la query, replica l’operazione come black box

Spesso, però, il testo della query non è affatto nel client.

Un’operazione persistita implementata correttamente lascia al client soltanto l’hash; la query in chiaro vive esclusivamente nel registro del server. Puoi setacciare il bundle in ogni modo e non trovarla, perché non è mai stata distribuita. Insistere con il metodo 1 in questo caso significa cercare qualcosa che non esiste.

Il metodo 2 è la soluzione più sottovalutata: non ti serve necessariamente la query in chiaro. L’obiettivo è ottenere la risposta, non conoscere l’albero dei campi della query. Registra quindi l’identificatore dell’operazione, hash o operationName, e il relativo involucro di variabili; inviali tali e quali, modificando solo i parametri di input che ti interessano. Non saprai quali campi sono stati selezionati, ma il server continuerà a restituirli. Per la grande maggioranza delle attività di raccolta dati e monitoraggio, è sufficiente.

YouTube innertube è l’esempio classico. Non è nemmeno GraphQL: usa un insieme fisso di endpoint autoesplicativi, youtubei/v1/{player,search,next}, con un body composto da un involucro context, che include tipo e versione del client, e da un gruppo di parametri. Nessuno prova a “ricostruire” il grafo interno delle query di YouTube: non sarebbe né possibile né utile. La procedura sensata è leggere una volta versione e context del client dalle risorse della pagina corrente, conservare quell’involucro identico nelle richieste successive e cambiare soltanto input come videoId o un termine di ricerca, chiamando l’endpoint fisso. La semantica dell’operazione resta una black box. Il caso in cui un valore chiave non sia nel codice statico e debba essere estratto dalle risorse runtime è una classe distinta di problema di reverse engineering, trattata separatamente.

Il vantaggio del metodo 2 è l’indipendenza dalla presenza della query nel client. Che tu abbia un hash o un involucro opaco, non cerchi di comprenderlo: lo riproduci con precisione. Il limite è che resti vincolato alle richieste già effettuate dal client. Se ti serve un campo che il client non richiede mai, la replica black box non può dartelo.

C’è un ulteriore caso che spezza la replica: l’involucro contiene una firma calcolata nuovamente a ogni richiesta e soggetta a scadenza. Qui la black box non basta più: devi ricostruire quel singolo campo, e riconoscere a quale famiglia di algoritmi appartenga la firma è il tema di un altro approfondimento.

Dove si collocano le piattaforme, e perché

Affiancando i due casi reali, scelta e motivazione diventano immediate:

Caso della piattaforma

Forma della richiesta

Query in chiaro nel client?

Approccio naturale

Motivo

GraphQL della hot-rank di una piattaforma di short video

/graphql + {operationName, variables, query}

Sì, direttamente nel body della richiesta

Metodo 1 (costo quasi nullo)

Nessuna operazione persistita, operationName leggibile e query in chiaro: una cattura contiene tutto

YouTube innertube

Endpoint fissi + involucro context

Nessuna query in chiaro di cui parlare

Metodo 2 (replica black box)

Non è GraphQL, non c’è una query da ricostruire: basta leggere una volta il context e riutilizzarlo identico

Il contrasto fra i due estremi dice una cosa sola: il metodo non lo scegli tu, lo impone il design dell’API della piattaforma. La prima concentra le proprie difese altrove, quindi può esporre la query in chiaro; qui basta raccoglierla. La seconda ha reso opaco il “cosa recuperare”, dunque non esiste testo da inseguire e l’unica strada è la replica.

L’ampia zona intermedia, cioè GraphQL con vere operazioni persistite, è dove serve più giudizio. La query può essere nel client, ad esempio in una mappatura incorporata nel bundle e quindi accessibile con il metodo 1, oppure può esistere soltanto sul server, lasciando al client il solo hash e rendendo preferibile il metodo 2. Prima di iniziare, devi classificare il caso.

La domanda da fare prima: ti serve davvero la query in chiaro?

La differenza di costo, pari a un ordine di grandezza, dipende interamente da questa valutazione.

Se una piattaforma distribuisce davvero soltanto l’hash e mantiene la query sul server, ostinarsi con il metodo 1 può portarti a scavare nel bundle per giorni, fino a scoprire che ciò che cercavi non è mai stato distribuito. Non è un problema di difficoltà, ma di direzione: nessuna quantità di lavoro può produrre un risultato.

Al contrario, se devi modificare la query, ad esempio per richiedere un campo che il client non usa mai, la replica black box del metodo 2 non può aiutarti. Devi tornare al metodo 1 per ottenere il testo della query, e resti bloccato se non riesci a recuperarlo.

L’ordine corretto, quindi, non è “come recupero la query?”, ma una domanda preliminare: mi serve davvero il testo in chiaro?

  • Devi solo riprodurre una richiesta che il client già effettua e leggerne la risposta: usa il metodo 2, la replica black box. È il più economico, il più spesso trascurato e funziona sia che la query sia disponibile sia che non lo sia. È il punto di partenza consigliato.

  • Devi cambiare il selection set o costruire una query che il client non invia: il metodo 1, cioè recuperare la query in chiaro, è necessario. Quanto sarà semplice dipende dal fatto che il client abbia distribuito o meno la mappatura. Se non l’ha fatto, ecco l’aumento di costo di un ordine di grandezza: devi accettarlo oppure rivalutare se la modifica della query sia davvero indispensabile.

Mettere questa decisione all’inizio evita gran parte dello spreco del tipo “partire dal metodo 1, restare bloccati tre giorni e capire solo dopo che serviva il metodo 2”. Il compromesso fra riscrivere il protocollo e accettare la black box è il tema dell’articolo sulla scala di purificazione; qui determina semplicemente quale approccio adottare.

Trovare la mappatura è retrieval a blocchi, non ragionamento

Il passaggio tecnicamente più impegnativo del metodo 1 è trovare dichiarazione e mappatura dell’operazione in decine di migliaia di righe di build frontend. È il punto in cui un modello può farti risparmiare tempo reale. Prima, però, bisogna chiarire che tipo di compito sia.

Non è un esercizio di ragionamento. Non ti serve che il modello capisca cosa calcola il codice; deve localizzare, all’interno di una grande quantità di testo, il blocco che dichiara la mappatura operationName-hash, il modulo che incorpora la query in chiaro oppure il punto in cui viene assemblato l’involucro context. È retrieval a blocchi: conta poter caricare abbastanza contesto e indicare con precisione il punto giusto, non costruire una catena argomentativa.

Prima esegui il passaggio meccanico di suddivisione: usa uno script per spezzare il build in moduli, indicizzarlo e filtrare polyfill e moduli di business non pertinenti. A quel punto consegna il risultato al modello: il compito diventa semplicemente “trova la dichiarazione in questi blocchi”.

In questo flusso, i quattro livelli si distinguono nettamente per capacità:

Passaggio

Capacità richiesta

Scelta

model id

Localizzare mappatura / dichiarazione dell’operazione in decine di migliaia di righe

Contesto lungo: deve assorbire un’ampia porzione del build e indicare il punto preciso

Kimi K3

kimi-k3

Scegliere fra metodo 1 e metodo 2, dedurre da pochi campioni quali campi dell’involucro cambiano

Ragionamento robusto: legge la struttura e valuta il compromesso

Claude Opus 5

claude-opus-5

Etichettare centinaia di operazioni, generare stub di replay, completare i tipi delle variabili

Economico, alta concorrenza

Claude Sonnet 5

claude-sonnet-5

Quando il replay non si connette, leggere il diff e attribuirne la causa (campo context mancante? versione dell’hash cambiata?)

Ragionamento intermedio: spiega in base alle differenze nella risposta

GPT-5.6 Sol

gpt-5.6-sol

Il primo livello è il terreno di questo articolo. Cambiare modello nella fase di localizzazione modifica visibilmente il risultato, perché il vincolo decisivo è la finestra di contesto. Il build contiene decine di migliaia di righe; un modello con poco contesto non può contenerle tutte e deve troncare l’input. Se il taglio esclude la mappatura, il suo “non la trovo” non significa che non sappia cercare: significa che non l’ha mai vista.

Non fidarti solo della teoria: verificalo.

  1. Cattura una richiesta reale e salva identificatore dell’operazione, operationName o hash, e variabili.

  2. Dividi il build frontend in blocchi con uno script, invialo insieme all’identificatore a kimi-k3 e chiedi di localizzare “dove viene dichiarata questa operazione e quale blocco contiene la query in chiaro corrispondente o la mappatura dell’hash”.

  3. Controlla un solo aspetto: la posizione indicata punta direttamente alla riga giusta? Colpito, mancato o punto vicino ma errato.

  4. Come controllo, invia lo stesso input a un modello con contesto corto e verifica se fallisce perché non riesce a contenere tutto. Il tasso di successo è il criterio con cui scegliere.

Un solo giro basta per vedere che, in questo tipo di compito, il contesto lungo non è “un po’ meglio”: è la differenza tra poterlo fare e non poterlo fare.

Il vero freno è il costo del cambio di modello

Quattro modelli, tre fornitori, tre SDK, tre sistemi di autenticazione e tre formati di errore. Per usare un modello diverso per retrieval, valutazione, lavoro in batch e attribuzione, l’approccio ingenuo è integrare tutti e tre i client. Molti fanno i conti, decidono che non ne vale la pena e restano su un solo modello per l’intero processo: usano così un livello che non riesce a contenere il build nella fase di retrieval a contesto lungo, per poi concludere che “il modello non riesce a trovarlo”.

AIReiter elimina questa complessità: una chiave, un’interfaccia compatibile con OpenAI e tutti e quattro i livelli dietro la stessa API. Per passare da uno all’altro basta modificare il campo model nel body della richiesta.

# Localizzare la mappatura: livello a contesto lungo, assorbe in una volta un ampio build diviso in blocchi
curl https://aireiter.com/api/v1/chat/completions \
  -H "Authorization: Bearer $AIREITER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kimi-k3",
    "messages": [{"role": "user", "content": "<build frontend diviso in blocchi + identificatore dell’operazione da localizzare>"}]
  }'

# Etichettare operazioni / generare stub di replay in batch: cambia il campo model, lascia invariato il resto
#   "model": "claude-sonnet-5"
# Attribuzione del replay:
#   "model": "gpt-5.6-sol"

Se usi già l’SDK OpenAI, punta base_url a https://aireiter.com/api/v1 e non modificare altro. Con l’SDK Anthropic, usa POST /api/v1/messages con la stessa chiave.

Sul fronte prezzi, i modelli Claude costano il 30% in meno del listino, i modelli GPT la metà, e Kimi K3 è utilizzabile con la stessa chiave. In questo flusso il costo si concentra in due punti: la localizzazione del metodo 1, che richiede di inviare l’intero build diviso in blocchi, con alcune centinaia di migliaia di token per input, a kimi-k3; e l’etichettatura di centinaia di operazioni con la generazione in batch degli stub di replay, il passaggio con più chiamate, su claude-sonnet-5 con il 30% di sconto. Lo sconto incide proprio sulla fase più densa di chiamate.

In sintesi

Un’API GraphQL senza documentazione non significa che non sia integrabile. Un’operazione persistita ha soltanto spostato il “cosa recuperare” fuori dalla richiesta, in uno di due luoghi: nel build del client, dove puoi trovarlo con il metodo 1, oppure soltanto sul server, dove non conviene inseguire la query e va replicata l’operazione come black box con il metodo 2.

I due approcci hanno costi distanti un ordine di grandezza. A determinare la scelta non è quale sembri più completo, ma due domande precedenti: la query in chiaro è nel client? E hai bisogno di modificarla? Rispondi a queste prima di iniziare e eviterai gran parte del lavoro sprecato.

Il ruolo del modello qui è circoscritto: nel metodo 1, trovare una dichiarazione in decine di migliaia di righe è puro retrieval. Un livello a contesto lungo può assorbire tutto in un passaggio e indicare il punto preciso, trasformando giorni di ricerca in minuti. Non decide al posto tuo quale metodo usare: quella è la valutazione che dovresti fare dopo aver letto questo articolo. Si occupa soltanto del lavoro ripetitivo di trovare la mappatura.