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
UNAUTHORIZED401Credenziali mancanti o non valide (una chiave API, o il token della sessione della dashboard), oppure una sessione della dashboard iniziata prima di una revoca delle sessioni dell'account (Sign in again.)Controlla l'header Authorization: Bearer; nella dashboard, accedi di nuovo
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
NO_WORKSPACE403L'account ha effettuato l'accesso ma non ha un workspaceCrea il workspace dalla dashboard, oppure accedi con l'account che appartiene al tuo workspace; vedi sotto
TENANT_SUSPENDED403Il workspace è sospeso: è in sola lettura, quindi non può inviare né modificare nulla. Arriva anche alla creazione di un workspace (POST /v1/workspaces) quando uno dei workspace posseduti dall'account è sospeso (Your account has a suspended workspace: a new one cannot be created.)Rispondi all'avviso di sospensione inviato al proprietario del workspace, oppure scrivi a support@tratto.email
TENANT_CLOSED403Il workspace è stato eliminato: le sue chiavi API vengono rifiutate, così come una sessione della dashboard il cui header Tratto-Workspace lo indicaNon c'è nulla da riprovare. Vedi Workspace
NOT_A_MEMBER403Una sessione della dashboard opera su un workspace a cui l'account non ha accesso: l'account ne è stato rimosso, oppure l'header Tratto-Workspace indica un workspace di cui l'account non è proprietario né membro attivoAccedi di nuovo per tornare al tuo workspace. Con l'header, invia l'id di un workspace a cui l'account ha accesso, oppure omettilo per usare quello predefinito. Vedi Autenticazione
REAUTH_REQUIRED401L'azione richiede un accesso più recente: degli ultimi 5 minuti per eliminare il workspace, o successivo all'ultima revoca delle sessioni dell'account per accettare un invitoAccedi di nuovo dalla dashboard, poi ripeti l'azione
MEMBER_INVITES_RESTRICTED403Gli inviti ai membri non sono disponibili per questo workspace: vengono aperti gradualmenteScrivi a support@tratto.email. Vedi Team
PLAN_UPGRADE_REQUIRED403Il piano del workspace non include la funzionalità. Sul Free: creare un webhook, inviare una consegna di prova o ruotarne il segreto (richiede l'API 0.23.0 o successiva); caricare un allegato o inviare un'email con attachments (richiede l'API 0.29.0 o successiva)Il proprietario dell'account passa a un piano superiore dalla dashboard in Impostazioni > Fatturazione (/settings/billing). Vedi Webhook e Allegati
EMAIL_NOT_VERIFIED403L'azione richiede un indirizzo email verificato sull'account (eliminazione del workspace)Verifica l'indirizzo: la dashboard offre l'invio dell'email di verifica
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, eliminare un template usato da un flusso o da una campagna non conclusa, o accettare un invito scaduto; i casi dei template sono in Template, i casi degli inviti sono elencati in Team). Su POST /v1/workspaces: un account che possiede già 10 workspace contando quelli in eliminazione (vedi Workspace)Recupera la risorsa e verificane lo stato prima di riprovare. Per la creazione di un workspace, scrivi a support@tratto.email
VALIDATION_ERROR422Il corpo è stato letto ma non rispetta lo schema, oppure l'header Tratto-Workspace non è un id di workspace validoPer il corpo, leggi details per i campi errati. Per l'header non c'è details: message è The Tratto-Workspace header is not a workspace id.; invia l'id di un workspace a cui l'account ha accesso, oppure ometti l'header. Vedi Autenticazione
TEMPLATE_NOT_PUBLISHED422Il template indicato nella richiesta è ancora in bozza e non può essere usato per inviarePubblicalo (PATCH /v1/templates/:id con status: "published"), poi ripeti la richiesta
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, 500 al minuto per IP client, 1 caricamento di allegato al secondo per chiave API, o troppe richieste simultanee sullo stesso accountAttendi e riprova, rispettando Retry-After se la risposta lo imposta
PAYLOAD_TOO_LARGE413Il corpo della richiesta supera la dimensione massima. Per POST /v1/attachments, un file oltre 25 MBSpezza il payload (per l'import contatti, carica meno righe per richiesta). Per un allegato, invia invece un link al file
QUOTA_EXCEEDED429Raggiunto un limite d'uso: la quota mensile di email (su POST /v1/emails quando l'email ha più destinatari distinti fra to, cc e bcc delle email che restano all'account, dove ogni destinatario conta come un'email, o inviando subito una campagna a più destinatari delle email che restano nella quota), il limite di domini (quando aggiungi un dominio), le generazioni AI mensili nella dashboard, il tetto giornaliero del test mode di 100 email di test, il tetto giornaliero di test-send per workspace (20 su Free, 100 su Starter e Growth, 200 su Scale, sui test-send di template e campagne, con l'API 0.24.0 o successiva), il limite di membri del team dell'account (all'invito o all'accettazione di un invito, inviti in attesa compresi, con l'API 0.28.0 o successiva), il numero di workspace che l'account può possedere (alla creazione di un workspace), o lo spazio per gli allegati del workspace, 1 GB (al caricamento di un allegato, con l'API 0.29.0 o successiva). La quota di email, il limite di domini, le generazioni AI, il limite di workspace e il limite di membri del team appartengono all'account e sono condivisi dai suoi workspaceNon riprovare: message indica il limite. La quota di email si azzera all'inizio del mese UTC successivo, il tetto del test mode e quello dei test-send a mezzanotte UTC. Per il limite di domini elimina un dominio o passa a un piano superiore; per la quota di email passa a un piano superiore; per il limite di persone revoca un invito in attesa, rimuovi un membro o passa a un piano superiore (vedi Team); per il limite di workspace elimina un workspace o passa a un piano superiore (vedi Workspace); per lo spazio degli allegati elimina quelli che non ti servono più o aspetta che alcuni scadano (vedi Allegati)
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. Sui test-send di campagne e template, un 503 significa che l'email non è stata messa in coda: non è stato creato, inviato o addebitato niente, quindi riprovare è sicuro. Su POST /v1/emails un 503 significa che non è stato possibile salvare l'email o consegnarla alla coda: non è partito niente, e il messaggio dice se c'è stato un addebito (Nothing was sent and nothing was charged. oppure, con l'API 0.21.0 o successiva quando non è stato possibile restituire la quota, Nothing was sent. This request may still count against the monthly quota.). Riprova dopo Retry-After, riusando la stessa Idempotency-Key se l'hai inviata

Una risorsa inesistente non ha un codice tutto suo. 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"
      }
    ]
  }
}

Con l'API 0.24.0 o successiva, POST /v1/emails e PATCH /v1/emails/{id} rifiutano anche un campo che non conoscono: la voce ha path / per un campo al primo livello del body, e un message che lo nomina, come Unrecognized key: "reply_to". Vedi Campi sconosciuti.

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.

TEMPLATE_NOT_PUBLISHED (422)

Un template deve essere published prima di poter essere usato per inviare. È un codice a sé, non una variante di VALIDATION_ERROR: non porta l'array details, perché nessuno schema ha rifiutato il corpo. Lo restituiscono POST /v1/campaigns, una PATCH /v1/campaigns/:id che sostituisce il template e POST /v1/flows/:id/activate:

{
  "error": {
    "code": "TEMPLATE_NOT_PUBLISHED",
    "message": "Template 'tmpl_abc123' is not published and cannot be used to send.",
    "docs": "https://docs.tratto.email/en/docs/error-codes",
    "suggestion": "Publish the template first: PATCH /v1/templates/:id with { \"status\": \"published\" }, or use the Publish button in the template editor."
  }
}

All'attivazione di un flow il messaggio indica anche lo step che usa il template: Template 'tmpl_abc123' on step 's0' is not published and cannot be used to send.

Un templateId inesistente è invece un NOT_FOUND (404). Il test-send del template è esente, e POST /v1/emails non viene rifiutata affatto: accetta la richiesta e l'email fallisce in consegna. Vedi Template.

NO_WORKSPACE (403)

Una sessione della dashboard (un account che ha effettuato l'accesso, non una chiave API) il cui account non ha un workspace riceve questo codice su ogni rotta autenticata, tranne le due che accettano un invito, quella che crea il workspace e quella che li elenca: GET /v1/workspaces risponde 200 con {"data":[]}, con o senza accesso alla beta. Prima viene il controllo dell'accesso alla beta: un account senza accesso alla beta riceve quell'errore.

{
  "error": {
    "code": "NO_WORKSPACE",
    "message": "This account has no workspace.",
    "docs": "https://docs.tratto.email/en/docs/error-codes"
  }
}

Non c'è suggestion. Accettare l'invito alla beta non crea un workspace, e nemmeno GET /v1/workspace. Nella dashboard questo codice apre la pagina «Questo account non ha un workspace»: il bottone «Crea il tuo workspace» lo crea e ti porta dentro. Da lì puoi anche accedere con un altro account o riprovare.

Il bottone chiama POST /v1/workspaces, che accetta solo una sessione della dashboard. Risponde 201 con lo stesso oggetto di GET /v1/workspace. Il campo name è facoltativo (da 1 a 100 caratteri, predefinito My Workspace): un nome non valido riceve 422. La stessa rotta crea i workspace oltre il primo, entro il limite del piano; i rifiuti sono elencati in Workspace. Gli id dei workspace creati così iniziano con ws_; i workspace esistenti tengono il loro id.

Le chiavi API non sono coinvolte: una chiave appartiene sempre a un workspace, e con una chiave non si crea un workspace (POST /v1/workspaces risponde 403).

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 destinatari. Riprovare non serve finché il limite non si azzera, non elimini un dominio o un workspace, non liberi un posto nel workspace o non passi a un piano superiore.

La quota mensile di email, il limite di domini e le generazioni AI appartengono all'account proprietario del workspace, e tutti i workspace di quell'account vi attingono. Un QUOTA_EXCEEDED può arrivare da un altro workspace dello stesso account o da un altro membro, e il suo message dice che il limite è condiviso dall'account. Vedi Rate Limit.

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" (uno dei quattro valori elencati in 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

Entrambi gli SDK sollevano un TrattoError che porta il code e lo status HTTP, e ognuno espone una parte del resto del payload: l'SDK Node aggiunge docs, quello Python aggiunge suggestion e docs dalla 1.2.0 o successiva. Dirama sul codice; leggi suggestion quando un codice copre più di una situazione.

Node.js

@tratto/email solleva TrattoError, che espone code, statusCode e docs:

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 'TEMPLATE_NOT_PUBLISHED':
        // Arriva dalle chiamate su campagne e flussi. Pubblica il template,
        // poi rifai la chiamata: riprovare così com'è non serve.
        break;
      case 'RATE_LIMITED':
        await backoff();
        break;
      case 'QUOTA_EXCEEDED':
        // Non c'è nulla da riprovare.
        break;
      default:
        throw error;
    }
  }
}

Python

tratto-email solleva TrattoError con code, status_code, suggestion e docs. Gli ultimi due richiedono la 1.2.0 o successiva e valgono None quando la risposta non li contiene:

from tratto import TrattoError

try:
    client.campaigns.unschedule('camp_xyz789')
except TrattoError as e:
    print(e.code)          # 'CONFLICT'
    print(e.suggestion)    # cosa fare invece, nelle parole dell'API
    print(e.docs)          # link a questa pagina

PHP

tratto/email solleva TrattoException con errorCode, statusCode, suggestion e docs. Gli ultimi due valgono null quando la risposta non li contiene:

use Tratto\TrattoException;

try {
    $tratto->campaigns->unschedule('camp_xyz789');
} catch (TrattoException $e) {
    echo $e->errorCode, "\n";   // 'CONFLICT'
    echo $e->suggestion, "\n";  // cosa fare invece, nelle parole dell'API
    echo $e->docs, "\n";        // link a questa pagina
}

È il caso per cui suggestion esiste: il 409 di campaigns.send e quello di campaigns.unschedule sono un codice solo per più stati diversi, e a dire quale hai incontrato c'è solo il testo. Vedi Campagne.


Prossimo: Rate Limit · Idempotenza


Modifica questa pagina su GitHub

Ultimo aggiornamento