Migrare da OpenAI o OpenRouter
Collega il codice OpenAI esistente a SpicyAPI: URL di base, chiave, ID del modello, differenze nei campi e impostazioni OpenRouter da rimuovere.
Se il codice chiama già un modello di chat tramite le librerie ufficiali OpenAI o uno strumento con un'impostazione «compatibile con OpenAI», per passare a SpicyAPI servono tre modifiche: URL di base, chiave API e ID del modello. Questa pagina elenca le modifiche, poi tutti i comportamenti diversi, così la migrazione non si conclude con un 400 inatteso. Per programmi di esempio completi, consulta Esempi pratici di chat.
Le tre modifiche
| Impostazione | Valore su SpicyAPI |
|---|---|
| URL di base | https://api.spicyapi.ai/v1 |
| Chiave API | Una chiave SpicyAPI (inizia con sk-spicy-), inviata come Authorization: Bearer |
| Modello | Un ID esatto di modello di testo dal catalogo aggiornato, per esempio in SPICY_MODEL (consulta ID dei modelli) |
import os
from openai import OpenAI
# Before: client = OpenAI() (reads OPENAI_API_KEY, calls api.openai.com)
client = OpenAI(api_key=os.environ["SPICY_API_KEY"], base_url="https://api.spicyapi.ai/v1")
reply = client.chat.completions.create(
model=os.environ["SPICY_MODEL"],
messages=[{"role": "user", "content": "Say hello in one sentence."}],
)
print(reply.choices[0].message.content)Le librerie OpenAI leggono anche OPENAI_BASE_URL e OPENAI_API_KEY dall'ambiente. Impostarle entrambe permette al codice esistente di funzionare senza modifiche, ma reindirizza anche ogni altra chiamata OpenAI nello stesso processo: preferisci quindi passare esplicitamente i valori, come sopra.
ID dei modelli
Gli ID dei modelli di testo hanno la forma publisher/model/chat. Copiali dal catalogo aggiornato invece di convertire un nome che conosci da un altro servizio:
GET /api/v1/models?modality=textelenca soltanto modelli di testo con i rispettivi prezzi e aggiunge l'inputSchemadi ciascuno conincludeSchema=1. Consulta Catalogo dei modelli e schemi.GET /v1/modelsè l'elenco nel formato OpenAI letto dagli strumenti. Senza un filtro include modelli di immagini, video e audio, che non possono essere chiamati su/v1/chat/completions; aggiungi?modality=textper avere soltanto modelli di testo. Oltre ai quattro campi OpenAI, ogni voce hamodalityetask; i modelli di chat hanno anchecontext_length,max_output_tokensesupported_parameters(i campi di richiesta accettati dal modello). Le librerie OpenAI ignorano i campi aggiuntivi.
Un ID di modello inesistente restituisce 404 con code: "model_not_found" e param: "model"; il messaggio mostra la struttura dell'ID e, se ne esiste uno, il modello di chat utilizzabile più simile. Chiamare un modello di immagini o video su un endpoint di chat restituisce 400, indicando che non è un modello di chat. Gli ID di altri servizi spesso sembrano simili ma non sono intercambiabili: i nomi dei creatori sono scritti diversamente, qui non esistono suffissi come :free e i modelli di testo terminano con /chat.
Campi della richiesta
Ogni modello pubblica i campi accettati nel proprio inputSchema (e i loro nomi in supported_parameters su /v1/models).
- I campi di campionamento non elencati dal modello vengono ignorati.
temperature,top_p,top_k,min_p,top_a,frequency_penalty,presence_penaltyerepetition_penaltycambiano soltanto la casualità o la ripetitività della risposta e molti client li inviano per impostazione predefinita: un modello che non li accetta genera semplicemente senza usarli. Lo stesso vale pern: 1, unostopvuoto,stopsu un modello senza sequenze di arresto ereasoning_effortsu un modello senza livelli di ragionamento. La risposta indica ciò che è stato ignorato nell'headerX-Spicy-Ignored-Params, per esempiofrequency_penalty, presence_penalty. - I valori non accettati dal modello vengono portati al valore accettato più vicino. Un valore di campionamento fuori intervallo viene limitato al confine dell'intervallo; una lista
stoppiù lunga di quella consentita conserva i primi elementi; un livelloreasoning_effortnon offerto viene sostituito con quello disponibile più vicino (quello inferiore in caso di parità). La risposta elenca ogni modifica comename=valuenell'headerX-Spicy-Adjusted-Params, per esempioreasoning_effort=low, temperature=1. - Ogni altro campo non elencato dal modello viene rifiutato con
400,code: "unsupported_parameter",paramimpostato sul campo e un messaggio che lo nomina, per esempiologprobs: is not a supported field. Questi campi cambiano ciò che ricevi, quindi non paghi mai una risposta generata senza un campo che credevi di aver inviato.
| Campo | Su SpicyAPI |
|---|---|
messages | Stessa struttura. Ruoli system, developer, user, assistant e tool. Il testo funziona ovunque; le parti image_url soltanto sui modelli il cui schema le elenca |
max_tokens, max_completion_tokens | Entrambi accettati, stesso significato. I token di ragionamento vengono conteggiati. Ogni modello ha il proprio valore predefinito e limite massimo |
stream, stream_options | Supportati, con le regole OpenAI sull'utilizzo: uno stream contiene l'utilizzo soltanto quando include_usage è true (consulta Risposte e stream) |
tools, tool_choice | Supportati su ogni modello di testo. Alcuni accettano soltanto auto e none per tool_choice, quindi forzare uno strumento per nome restituisce 400 su questi modelli |
parallel_tool_calls | Soltanto sui modelli che lo elencano |
reasoning_effort | Sulla maggior parte dei modelli, ma i valori consentiti variano per modello: alcuni aggiungono none, minimal, xhigh o max, altri non hanno medium. Un valore non offerto viene portato a quello disponibile più vicino, quindi leggi l'enum del modello se il livello esatto è importante |
temperature, top_p, top_k, frequency_penalty, presence_penalty | Usati sui modelli che li elencano, limitati all'intervallo del modello, ignorati sugli altri (indicati in X-Spicy-Ignored-Params). Vari modelli non accettano alcun campo di campionamento |
seed | Soltanto sui modelli che lo elencano |
stop | Usato sui modelli che lo elencano, accorciato al numero massimo previsto; ignorato sui modelli senza sequenze di arresto e quando è vuoto |
response_format | json_object sui modelli che elencano il campo; non disponibile sugli altri |
user, metadata, store | Accettati e ignorati |
n | 1 viene ignorato; qualsiasi altro valore viene rifiutato con 400 sui modelli che non lo elencano |
logprobs, top_logprobs, logit_bias e ogni altro campo non elencato dal modello | Rifiutati con 400 |
Per vedere campi e valori consentiti di un modello prima di inviare qualcosa, esegui il comando in Esempi pratici di chat: prima di iniziare.
Risposte e stream
Una risposta senza streaming è un normale oggetto chat.completion, quindi reply.choices[0].message.content e reply.usage funzionano come prima. Le differenze sono piccole:
reasoning_content. I modelli che ragionano prima di rispondere restituiscono quel testo inmessage.reasoning_content(oppuredelta.reasoning_contentin streaming), accanto acontent. È già conteggiato inusage.completion_tokens.- L'ID è quello della richiesta.
idha la formachatcmpl-req_...e l'header di rispostax-request-idcontiene lo stesso valorereq_.... Citalo quando contatti l'assistenza. Un secondo header,X-Spicy-Task-Id, identifica l'attività che conserva l'addebito (consulta Costo e utilizzo). - Nessun
system_fingerprintnélogprobs.
Gli stream usano gli stessi eventi chat.completion.chunk, terminano con data: [DONE] e seguono le regole OpenAI sull'utilizzo:
- Utilizzo soltanto su richiesta. Senza
stream_options.include_usage, nessun frammento contieneusagee ogni frammento ha una listachoicesnon vuota. Con"include_usage": true, arriva un frammento aggiuntivo con una listachoicesvuota e l'usagefinale poco prima di[DONE]; ogni altro frammento hausage: null. Il codice scritto sugli esempi di streaming OpenAI funziona senza modifiche; controllarechoicesprima di leggerechoices[0]lo rende sicuro su quell'ultimo frammento. - Un
finish_reasonper scelta. Ogni scelta termina con esattamente un frammento che contiene il suofinish_reason, qualunque sia il modello chiamato. - Righe keep-alive. Mentre un modello sta ancora ragionando, lo stream contiene righe di commento
: keep-aliveper mantenere aperta la connessione. Le librerie OpenAI le saltano; anche un parser scritto a mano deve farlo.
Per risposte lunghe o reasoning_effort elevato, usa lo streaming. Una richiesta senza streaming che dura oltre circa 30 secondi viene mantenuta aperta con spazi prima del corpo JSON e l'header X-Spicy-Keepalive: 1, così le librerie OpenAI riescono ancora a interpretarla; ma se fallisce dopo quel momento, l'oggetto di errore arriva nel corpo di una risposta 200, che le librerie non segnalano come errore. Consulta Timeout e streaming.
Errori
Gli errori usano il formato OpenAI, {"error": {"message", "type", "param", "code"}}, con i consueti codici di stato HTTP, quindi le librerie OpenAI sollevano le eccezioni abituali. param identifica il campo interessato come percorso con punti, per esempio temperature o messages.1.content.0, e code distingue errori con lo stesso stato: unsupported_parameter (un campo o una parte di messaggio non accettati), invalid_request (campo mancante o valore non accettato), content_rejected (rifiuto per politiche sui contenuti, anch'esso 400) e model_not_found (404). Una differenza rispetto a OpenAI: 402 viene usato quando il saldo o un limite di spesa non copre la richiesta. La tabella completa, con le azioni per ogni codice, è in Esempi pratici di chat: quando qualcosa non funziona.
Le librerie OpenAI riprovano autonomamente le risposte 429 e 5xx. Qui è sicuro: una richiesta fallita non viene addebitata. Se invii un Idempotency-Key, passalo per ogni chiamata e leggi prima Chiavi di idempotenza sugli endpoint di testo.
Costo e utilizzo
Le risposte riportano conteggi di token in usage, non un prezzo. I prezzi per milione di token di input, input in cache e output sono elencati sulla pagina di ogni modello e nel catalogo. Per sapere il costo effettivo di una chiamata, leggi l'header di risposta X-Spicy-Task-Id (job_…), presente su ogni richiesta accettata, con o senza streaming, e cerca l'attività con GET /api/v1/jobs/recordInfo?taskId=<job_…>: cost è finale quando settled è true. Le voci di GET /api/v1/jobs contengono anche il requestId della chiamata che le ha create (l'header X-Request-Id), e Console → Log mostra le stesse attività. Come leggere l'header con le librerie OpenAI è mostrato in Esempi pratici di chat: trovare il costo di una chiamata; consulta anche Preventivi e fatturazione.
Prima di eseguire una richiesta, viene bloccato dal saldo un importo che copre i messaggi e l'intero max_tokens di output (il valore predefinito del modello se lo ometti). Paghi soltanto ciò che viene usato, ma il blocco deve prima essere coperto: un saldo piccolo con un grande max_tokens restituisce quindi 402. Imposta un max_tokens realistico oppure limiti di spesa sulla chiave; consulta Fatturazione.
Endpoint che non troverai qui
La generazione di testo è disponibile come OpenAI Chat Completions, OpenAI Responses, Anthropic Messages e Google Gemini; i dettagli sono in Testo e streaming. Questi endpoint OpenAI non esistono e restituiscono 404: /v1/embeddings, il vecchio /v1/completions, /v1/images/*, /v1/audio/*, /v1/moderations, /v1/files, /v1/batches, /v1/assistants e il fine-tuning. I modelli di immagini, video e audio vengono chiamati attraverso l'API delle attività, mentre i video anche attraverso /v1/videos.
Arrivare da OpenRouter
Anche OpenRouter usa il formato OpenAI, quindi valgono le tre modifiche sopra: sostituisci l'URL di base https://openrouter.ai/api/v1 con https://api.spicyapi.ai/v1 e cambia chiave e ID dei modelli. Poi rimuovi i campi presenti soltanto su OpenRouter: qui restituiscono 400.
| Campo OpenRouter | Cosa fare |
|---|---|
models (un elenco di fallback) e route | Rimuovi. Invia un solo ID di modello; SpicyAPI sceglie come servirlo |
provider | Rimuovi. Non esiste una selezione del fornitore per ogni richiesta |
reasoning (un oggetto come {"effort": "high"}) | Convertito nel reasoning_effort al livello principale (enabled: false diventa none dove il modello lo prevede); le altre chiavi al suo interno vengono ignorate. Inviare direttamente reasoning_effort è più chiaro |
usage ({"include": true}) | Rimuovi. Le risposte senza streaming includono sempre usage; per gli stream invia stream_options: {"include_usage": true} |
transforms, plugins | Rimuovi |
Gli header HTTP-Referer e X-Title sono innocui e ignorati.
Checklist
- URL di base
https://api.spicyapi.ai/v1e una chiave SpicyAPI. - ID di modello copiato dal catalogo aggiornato, che termina con
/chat. - Campi della richiesta controllati rispetto all'
inputSchemadel modello; campi non di campionamento non supportati rimossi; header di rispostaX-Spicy-Ignored-ParamseX-Spicy-Adjusted-Paramscontrollati una volta per vedere cosa sia stato cambiato. - Se invii
stream_options.include_usage, i cicli di streaming saltano il frammento finale con la listachoicesvuota. max_tokensimpostato su un valore realistico, oppure lasciato al valore predefinito del modello, con limiti di spesa sulla chiave.- Le richieste lunghe o con ragionamento elevato usano
stream: true.
Continua a leggere
Esempi pratici di chat · Testo e streaming · Catalogo dei modelli e schemi · Autenticazione · Fatturazione
Esempi pratici di chat
Programmi completi eseguibili per conversazioni, roleplay, streaming nell'interfaccia e chiamate a strumenti, in Python, Node.js e curl.
Tutorial: da immagine a video
Parti da un’immagine sul tuo computer e arriva a un file video scaricato. Ogni passaggio mostra la CLI, l’SDK TypeScript e l’HTTP puro.

