Riferimento Codici di Errore
I codici di errore restituiti dall'API Tratto, e cosa fare per ciascuno.
L'API restituisce un piccolo insieme di codici di errore. Sono volutamente generici: il codice
indica la classe del problema e lo stato HTTP, mentre message contiene il
dettaglio specifico.
Fai i controlli su code, non su message: i messaggi sono scritti per le
persone e possono cambiare senza preavviso.
Formato degli Errori
{
"error": {
"code": "NOT_FOUND",
"message": "Webhook 'wh_abc123' not found.",
"docs": "https://docs.tratto.email/en/docs/error-codes"
}
}suggestion è presente su alcuni errori con un'indicazione su come risolvere.
Gli errori di validazione aggiungono un array details: vedi sotto.
I Codici
| Codice | HTTP | Significato | Cosa fare |
|---|---|---|---|
UNAUTHORIZED | 401 | Chiave API mancante o non valida | Controlla l'header Authorization: Bearer |
FORBIDDEN | 403 | La richiesta non è consentita: alla chiave manca un permesso, il dominio mittente non è verificato, o l'azione è bloccata per questa risorsa | Leggi message e suggestion; se manca un permesso, usa una chiave che lo abbia |
NOT_FOUND | 404 | La risorsa non esiste | Verifica l'ID: potrebbe appartenere a un altro workspace |
CONFLICT | 409 | La risorsa esiste già, oppure il suo stato attuale non consente l'azione (ad esempio modificare una campagna che non è più in bozza) | Recupera la risorsa e verificane lo stato prima di riprovare |
VALIDATION_ERROR | 422 | Il corpo è stato letto ma non rispetta lo schema | Leggi details per i campi errati |
BAD_REQUEST | 400 | La richiesta non è arrivata a un handler: il corpo non è interpretabile (JSON malformato) | Correggi la sintassi del corpo: il payload non è mai arrivato allo schema |
UNSUPPORTED_MEDIA_TYPE | 415 | Il Content-Type non è fra quelli che l'endpoint sa leggere | Usa il tipo atteso (application/json nella maggior parte dei casi, text/csv per l'import contatti, image/png o image/jpeg per il logo del marchio) |
INVALID_TOKEN | 400 | Un link firmato (disiscrizione, pagina preferenze) non è valido | Non c'è nulla da riprovare: serve un link nuovo |
RATE_LIMITED | 429 | Troppe richieste: 100 al secondo per chiave API, o 500 al minuto per IP client | Applica un backoff esponenziale e riprova |
PAYLOAD_TOO_LARGE | 413 | Il corpo della richiesta supera la dimensione massima | Spezza il payload (per l'import contatti, carica meno righe per richiesta) |
QUOTA_EXCEEDED | 429 | Raggiunto un limite d'uso: la quota mensile di email (su POST /v1/emails, o inviando subito una campagna a più destinatari di quanti ne restano nella quota), il limite di domini (quando aggiungi un dominio), o il tetto giornaliero del test mode | Non riprovare: message indica il limite. La quota email si azzera all'inizio del mese UTC successivo, il tetto del test mode a mezzanotte UTC. Per il limite di domini elimina un dominio o passa a un piano superiore; per la quota email passa a un piano superiore |
TEST_MODE_NOT_SUPPORTED | 403 | L'endpoint raggiunge destinatari reali e rifiuta le chiavi di test | Usa una chiave live (tratto_live_...) — vedi Test mode |
IDEMPOTENCY_CONFLICT | 409 | La stessa Idempotency-Key è stata riusata con un payload diverso | Usa una chiave nuova per una richiesta diversa |
IDEMPOTENCY_IN_PROGRESS | 409 | Una richiesta con questa Idempotency-Key è ancora in elaborazione | Attendi che la prima finisca, poi riprova |
INTERNAL_ERROR | 500 | Errore dalla nostra parte | Riprova con backoff; contatta il supporto se persiste |
SERVICE_UNAVAILABLE | 503 | Un servizio da cui dipende l'endpoint è disattivato, saturo o non risponde | Riprova più tardi, rispettando Retry-After se la risposta lo imposta; suggestion indica se attendere serve |
Non esistono codici specifici per risorsa. Un template inesistente, un contatto
inesistente e una campagna inesistente restituiscono tutti NOT_FOUND: la
risorsa è indicata in message, non in code.
BAD_REQUEST (400) e VALIDATION_ERROR (422)
Entrambi significano «la tua richiesta è sbagliata», ma falliscono in due momenti diversi:
BAD_REQUEST(400) — la richiesta non è mai arrivata a un handler. Il corpo non è stato interpretabile: JSON malformato, payload troncato, unContent-Typeche dichiara JSON su qualcosa che non lo è. Non c'è arraydetails, perché nessuno schema è stato eseguito.VALIDATION_ERROR(422) — il corpo è stato letto e poi ha fallito lo schema. È questo a dirti quale campo è sbagliato.
Cambiato il 2026-09-04. Tre casi rispondevano 500 INTERNAL_ERROR e non lo
fanno più: un corpo vuoto inviato con Content-Type: application/json — la
forma che curl e Postman producono di default per una mutazione senza campi,
come POST /v1/campaigns/{id}/pause, POST /v1/campaigns/{id}/unschedule o
qualunque DELETE — ora va a buon fine normalmente (200/204/409); il JSON
malformato ora risponde 400 BAD_REQUEST; un Content-Type non gestito
ora risponde 415 UNSUPPORTED_MEDIA_TYPE. Se il tuo client tratta i 5xx come
riprovabili e i 4xx come definitivi, per questi tre casi la classificazione ora
è corretta.
Errori di validazione
VALIDATION_ERROR restituisce 422 e include un array details
prodotto dal validatore dello schema, che identifica ogni campo non valido:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed.",
"docs": "https://docs.tratto.email/en/docs/error-codes",
"details": [
{
"path": "/to",
"message": "Invalid email"
}
]
}
}Validazione Markdown
Il supporto markdown su POST /v1/emails e sui
template aggiunge due casi di VALIDATION_ERROR (422) da conoscere:
markdownehtmlinsieme. I due campi sono mutuamente esclusivi ovunque. SuPOST /v1/emailsil messaggio è"markdown and html are mutually exclusive — provide one or the other."; sui template,htmlviene rifiutato del tutto performat: "emailmd"("html is not accepted when format is 'emailmd' — the HTML is derived from the markdown render.") emarkdownrichiede quel formato.- Markdown vuoto. Un campo
markdownvuoto o di soli spazi fallisce con"markdown must not be empty."— il render non gira mai su input vuoto.
Due codici condividono il 429
RATE_LIMITED e QUOTA_EXCEEDED restituiscono entrambi 429, ma richiedono
reazioni opposte:
RATE_LIMITED: stai inviando troppo in fretta. Rallenta e riprova: la richiesta andrà a buon fine a breve. Vedi Rate Limit.QUOTA_EXCEEDED: hai raggiunto un limite d'uso, come la quota mensile di email. Riprovare non serve finché il limite non si azzera, non elimini un dominio o non passi a un piano superiore.
Il lavoro in background non risponde mai con questo codice. Una campagna che il
dispatcher non riesce a far stare nella quota rimasta, prima di partire o a
metà invio, viene messa in pausa con pausedReason: "quota_exceeded" (vedi
Campagne); riprenderla più tardi raggiunge solo i
destinatari mancati. Un flusso il cui step di invio trova la quota esaurita
salta quell'email e prosegue: l'email non viene inviata in seguito.
Trattarli allo stesso modo significa riprovare all'infinito contro un muro:
if (error.status === 429) {
if (error.code === 'QUOTA_EXCEEDED') {
// Avvisa qualcuno. Riprovare non può risolvere.
throw error;
}
await backoff();
return retry();
}Gestione degli errori con l'SDK
@tratto/email solleva TrattoError, che espone code e statusCode:
import { TrattoError } from '@tratto/email';
try {
await tratto.emails.send({ /* … */ });
} catch (error) {
if (error instanceof TrattoError) {
switch (error.code) {
case 'VALIDATION_ERROR':
// Correggi il payload: riprovare invariato fallirà di nuovo.
break;
case 'RATE_LIMITED':
await backoff();
break;
case 'QUOTA_EXCEEDED':
// Non c'è nulla da riprovare.
break;
default:
throw error;
}
}
}Prossimo: Rate Limit · Idempotenza
Modifica questa pagina su GitHub
Ultimo aggiornamento