Convenzioni#

Valgono per l’intera superficie. Leggerle una volta evita di riscoprirle risorsa per risorsa.

Versionamento#

La versione è nel percorso: /api/public/v1/.

Le modifiche additive — nuovi campi, nuovi endpoint, nuovi parametri facoltativi, nuovi valori di enum — vengono rilasciate dentro v1. Il tuo client deve tollerarle, e questo richiede due abitudini: ignora i campi di risposta che non riconosci e non bloccarti davanti a un valore di enum sconosciuto.

Rimozioni, ridenominazioni, cambi di tipo e tutto ciò che rende più stretta la convalida fanno salire la versione, con una finestra di sovrapposizione durante la quale entrambe sono attive.

Risposte degli elenchi#

Gli elenchi sono paginati a cursore e tornano sempre nella stessa busta:

{
  "object": "list",
  "data": [ { "object": "shift", "id": "0194d1c0-…", "…": "…" } ],
  "has_more": true,
  "next": "https://app.shiftavo.com/api/public/v1/shifts/?cursor=…"
}
  • next è un URL opaco. Seguilo così com’è; non costruire né analizzare i cursori.

  • Fermati quando has_more è false.

  • page_size controlla la lunghezza della pagina.

Il discriminatore object#

Ogni risorsa porta un campo "object" che ne indica il tipo — "shift", "location", "employee". Ramifica su quello invece che sulla forma del payload, in particolare negli handler dei webhook e nelle collezioni mescolate.

Errori#

Una sola busta, sempre:

{
  "error": {
    "type": "conflict_error",
    "code": "not_cancellable",
    "message": "…",
    "details": [],
    "param": "start_date"
  }
}
  • details è sempre presente: un elenco di errori a livello di campo, vuoto quando l’errore non ha una scomposizione per campo.

  • param compare solo quando è in errore esattamente un campo.

Ci sono sette tipi. Ciascuno corrisponde a uno stato, tranne invalid_request_error, il contenitore generico per i 4xx non tabulati, dove la distinzione è portata dal code:

type

Stato

Note

invalid_request_error

400 · 405 · 406 · 415

code = invalid_request / method_not_allowed / not_acceptable / unsupported_media_type

idempotency_error

400

Idempotency-Key riutilizzata con un corpo diverso (idempotency_key_reused)

authentication_error

401

token mancante / non valido / scaduto

permission_error

403

insufficient_scope e simili

not_found

404

restituito anche invece di 403 per i record al di fuori della tua azienda — nessuna fuga di informazioni sull’esistenza

conflict_error

409

conflitti di stato: duplicate, in_use, not_deletable, una riproduzione idempotente ancora in corso

rate_limit_error

429

porta Retry-After

Ramifica su type per la classe di gestione, su code per il caso specifico. Entrambi sono stabili; message non lo è — consideralo solo come testo leggibile da una persona.

Idempotenza#

Invia un header Idempotency-Key sulle creazioni. Una richiesta ripetuta riproduce così il primo risultato invece di creare un secondo record.

Riutilizzare una chiave con un corpo diverso è un 400 idempotency_key_reused: la chiave identifica una richiesta, non un endpoint.

Tracciamento delle richieste#

Ogni risposta, di successo o di errore, porta un header X-Request-Id. Registralo. Se invii il tuo X-Request-Id, viene restituito così com’è, in modo che un id di traccia possa viaggiare da un’estremità all’altra. Citalo in qualsiasi richiesta di assistenza.

Transizioni del ciclo di vita#

Una transizione si attiva con un POST verso un sotto-percorso di azione: POST …/<id>/publish/, …/cancel/, …/revoke/.

Una transizione non applicabile — annullare una bozza, pubblicare qualcosa già pubblicato — è un 409 con un codice specifico (not_cancellable, not_publishable, not_unpublishable). Non è mai un 200 che non ha fatto nulla in silenzio.

Eliminazione#

DELETE restituisce uno stub, non un 204:

{ "id": "0194d1c0-…", "object": "shift", "deleted": true }

Un DELETE non accetta mai un corpo. Tutto ciò che serve a un’eliminazione sta nel percorso o nella stringa di query, così un client generato può esprimerle tutte. (HTTP lascia indefinita la semantica del corpo di un DELETE e OpenAPI 3.0 dice ai consumatori di ignorarlo, quindi un campo del corpo qui scomparirebbe silenziosamente dal tuo client e tornerebbe come un 400 che lo schema non saprebbe spiegare.)

L’eliminazione non è il verbo delicato#

Quando una risorsa offre entrambi, non sono sinonimi e la scelta sbagliata non è recuperabile:

Risorsa

DELETE …/<id>/

Il verbo delicato

shifts

rimuove il turno; le persone assegnate a un turno pubblicato vengono avvisate e ogni offerta attiva nel mercato viene prima ritirata

POST …/cancel/ — il turno rimane, contrassegnato come annullato

employees

rimozione definitiva: l’accesso viene revocato, la persona esce da ogni collezione e un GET successivo è un 404. Il record viene conservato internamente, così i rapporti delle ore e le assenze mantengono i loro riferimenti

POST …/deactivate/ (reversibile) · POST …/offboard/ (fine rapporto con data + liquidazione)

leave_requests

solo se in attesa o rifiutata — una richiesta confermata o revocata comporta una registrazione nel giornale, quindi è un 409 not_deletable

POST …/revoke/ — annulla l’effetto, conserva il record

Record ricorrenti#

Eliminare una singola occorrenza di un record ricorrente shifts o availability richiede un ambito esplicito:

DELETE …/<id>/?scope=this|following|all        # shifts
DELETE …/<id>/?scope=single|following|all      # availability

Se lo ometti su una serie ottieni un 409 delete_scope_required invece di un’ipotesi su cosa intendevi.

Limiti di frequenza#

Un 429 porta un header Retry-After. Rispettalo e applica un backoff esponenziale in caso di ripetizioni.