← Blog
engineeringarchitecturebackendapi

Il design delle API è UX

24 marzo 2026

Il design delle API è UX

Non pubblicheresti una UI con pulsanti dalle etichette imprevedibili, form che non danno feedback quando falliscono e una navigazione che funziona in modo diverso su ogni pagina. Eppure gli sviluppatori pubblicano API così tutti i giorni — e poi si chiedono perché le integrazioni siano lente, le richieste di supporto si accumulino e nessuno usi l'SDK.

Un'API è un prodotto. Lo sviluppatore che la chiama è il tuo utente. Tutto quello che sai sulla UX si applica.

Chi chiama è un utente

Quando uno sviluppatore colpisce il tuo endpoint, non sta guardando del codice — sta guardando un'interfaccia. Ha un modello mentale di cosa dovrebbe fare. Legge il nome della route, forma un'aspettativa e invia la richiesta. Se la risposta lo sorprende, ha avuto una cattiva esperienza, nello stesso modo in cui un utente che clicca un pulsante che fa qualcosa di inaspettato ha avuto una cattiva esperienza.

La differenza è che una UI cattiva fallisce nel momento dell'interazione. Un'API mal progettata fallisce nel momento dell'integrazione — che potrebbe essere giorni dopo l'inizio del progetto — e poi continua a fallire ogni volta che qualcuno di nuovo legge il codice client e cerca di capire cosa fa.

Una cattiva UX è un problema una volta. Una cattiva DX è un problema per sempre.

Il nome è la prima impressione

Il nome di un endpoint è la prima cosa che uno sviluppatore legge. Stabilisce l'aspettativa che tutto il resto deve soddisfare.

POST /users/create è ridondante — POST implica già la creazione. GET /user quando si restituisce una lista è una bugia. POST /do-the-thing è uno scherzo, ma l'ho visto in produzione. Non sono pedanterie. Una route mal nominata costringe chi la chiama a tenere in testa due cose: cosa dice il nome e cosa fa davvero la route. È carico cognitivo che aggiungi a ogni integrazione, per sempre.

La regola è semplice: i nomi devono dire esattamente cosa succede. POST /users crea un utente. GET /users/:id ne restituisce uno. DELETE /users/:id lo rimuove. Se non riesci a nominare una route senza ambiguità, la route sta probabilmente facendo troppe cose.

Gli errori sono comunicazione

Niente dice di più a uno sviluppatore sulla qualità di un'API di quello che succede quando qualcosa va storto.

Un 500 Internal Server Error con il body vuoto dice: non abbiamo pensato a questo caso. Un 400 Bad Request con il messaggio "Bad Request" dice: ci abbiamo pensato, ma non a te. Un 400 con "Il campo 'email' è obbligatorio e non è stato fornito" dice: l'abbiamo progettata per qualcuno che la usasse davvero.

Le risposte di errore sono la parte più importante di un'API da fare bene, perché sono quelle che chi la chiama legge quando le cose non funzionano — esattamente quando ha più bisogno di chiarezza. Il messaggio di errore dovrebbe dire cosa è andato storto, perché è andato storto e idealmente cosa fare. Tutto il resto è attrito.

Contano anche gli status code HTTP. Un 200 che restituisce { "success": false } è attivamente dannoso — rompe ogni client che si smista sullo status. Usa i codici come previsto. 401 significa non autenticato. 403 significa non autorizzato. 404 significa non trovato. 422 significa che l'input era comprensibile ma non valido. Non sono suggerimenti.

Com'è fatta bene

Una spec OpenAPI è un'ottima lente perché ti obbliga ad articolare ogni decisione: il nome della route, i campi obbligatori, quelli opzionali, le possibili risposte. Ecco come appare un endpoint ben progettato scritto per esteso:

paths:
  /users:
    post:
      summary: Crea un utente
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, name]
              properties:
                email:
                  type: string
                  format: email
                name:
                  type: string
                role:
                  type: string
                  enum: [member, admin]
                  default: member
      responses:
        "201":
          description: Utente creato
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
        "400":
          description: Errore di validazione — un campo obbligatorio manca o non è valido
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Esiste già un utente con questa email
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

components:
  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            message:
              type: string
            field:
              type: string

Alcune cose da notare: POST /users, non POST /users/create. 201 in caso di successo, non 200. Due codici di errore distinti — 400 per input non valido, 409 per un conflitto — ciascuno con una descrizione leggibile. role è opzionale con un default, quindi chi chiama non deve pensarci a meno che non gli interessi. E la struttura dell'errore è la stessa in ogni risposta, così il client la gestisce una volta sola.

Niente di brillante. Solo coerente, specifico e onesto su cosa può andare storto.

Default ragionevoli

Un'API ben progettata funziona out of the box con l'input minimo necessario. I parametri opzionali devono essere opzionali. La paginazione deve avere un default ragionevole. L'ordinamento deve avere un default prevedibile.

Se chi chiama deve passare sette parametri solo per fare una richiesta base, hai preso una decisione al suo posto: il suo caso d'uso non conta a meno che non abbia letto tutta la documentazione prima. È l'equivalente API di un form con trenta campi obbligatori.

I default sono una decisione di design. Dicono: questo è quello che ci aspettiamo che la maggior parte di chi chiama voglia. Azzeccali e il caso comune richiede quasi nessuna configurazione. Sbaglialo e ogni chiamante scrive lo stesso boilerplate per fare override.

La coerenza è la virtù principale

La qualità più importante in un'API non è la brillantezza o la completezza delle funzionalità. È la coerenza.

Se pagini un endpoint con page e per_page, paginali tutti in quel modo. Se restituisci le date in ISO 8601 su una route, restituiscile in ISO 8601 ovunque. Se la tua struttura di errore è { "error": { "message": "..." } }, non restituire { "message": "..." } su una route diversa perché l'hai scritta un altro giorno.

L'incoerenza costringe chi chiama a gestire ogni endpoint come un caso speciale. Rende il codice client fragile e il codebase difficile da navigare. Suggerisce che nessuno stava pensando all'API come a un insieme — solo pubblicando endpoint uno alla volta senza una prospettiva unitaria.

Un'API coerente può avere lacune. Può mancare di funzionalità. Chi la chiama lavorerà intorno alle lacune. Non può lavorare intorno all'incoerenza senza codificarla in ogni client che scrive.

L'API che pubblichi è quella che devi mantenere

Ogni endpoint che pubblichi è una promessa. Chi la chiama ci costruirà sopra. Scriverà codice che dipende dalla forma esatta della risposta, dalla semantica esatta degli status code, dai nomi esatti dei campi. Cambiare qualsiasi cosa in seguito è una breaking change — un costo che pagherai in versionamento, guide di migrazione e cicli di deprecazione.

Per questo il design merita una riflessione seria prima che la prima risposta esca mai dal server. Non perché le API siano difficili da scrivere, ma perché sono difficili da cambiare. L'implementazione può essere refactorizzata liberamente. Il contratto è permanente finché non sei disposto a romperlo.

Progettala per chi la chiama. La chiameranno a lungo.