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

CodiceHTTPSignificatoCosa fare
UNAUTHORIZED401Chiave API mancante o non validaControlla l'header Authorization: Bearer
FORBIDDEN403La richiesta non è consentita: alla chiave manca un permesso, il dominio mittente non è verificato, o l'azione è bloccata per questa risorsaLeggi message e suggestion; se manca un permesso, usa una chiave che lo abbia
NOT_FOUND404La risorsa non esisteVerifica l'ID: potrebbe appartenere a un altro workspace
CONFLICT409La 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_ERROR422Il corpo è stato letto ma non rispetta lo schemaLeggi details per i campi errati
BAD_REQUEST400La 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_TYPE415Il Content-Type non è fra quelli che l'endpoint sa leggereUsa 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_TOKEN400Un link firmato (disiscrizione, pagina preferenze) non è validoNon c'è nulla da riprovare: serve un link nuovo
RATE_LIMITED429Troppe richieste: 100 al secondo per chiave API, o 500 al minuto per IP clientApplica un backoff esponenziale e riprova
PAYLOAD_TOO_LARGE413Il corpo della richiesta supera la dimensione massimaSpezza il payload (per l'import contatti, carica meno righe per richiesta)
QUOTA_EXCEEDED429Raggiunto 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 modeNon 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_SUPPORTED403L'endpoint raggiunge destinatari reali e rifiuta le chiavi di testUsa una chiave live (tratto_live_...) — vedi Test mode
IDEMPOTENCY_CONFLICT409La stessa Idempotency-Key è stata riusata con un payload diversoUsa una chiave nuova per una richiesta diversa
IDEMPOTENCY_IN_PROGRESS409Una richiesta con questa Idempotency-Key è ancora in elaborazioneAttendi che la prima finisca, poi riprova
INTERNAL_ERROR500Errore dalla nostra parteRiprova con backoff; contatta il supporto se persiste
SERVICE_UNAVAILABLE503Un servizio da cui dipende l'endpoint è disattivato, saturo o non rispondeRiprova 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, un Content-Type che dichiara JSON su qualcosa che non lo è. Non c'è array details, 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:

  • markdown e html insieme. I due campi sono mutuamente esclusivi ovunque. Su POST /v1/emails il messaggio è "markdown and html are mutually exclusive — provide one or the other."; sui template, html viene rifiutato del tutto per format: "emailmd" ("html is not accepted when format is 'emailmd' — the HTML is derived from the markdown render.") e markdown richiede quel formato.
  • Markdown vuoto. Un campo markdown vuoto 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