← Developers

Esempi end-to-end

Spec riferita a api v1

Ogni snippet qui sotto colpisce endpoint reali — niente mock. I flussi edge puntano al runtime del nodo (http://<edge>:8291, il verticale manifests/LayoutManifest.production-line.example.json), i flussi cloud puntano al Control Plane (https://cp.axiom-trace.eu). Se non hai ancora letto Il Contratto, leggilo prima.

1 — Sessione operatore (edge)

# Login PIN → token di sessione (TTL in expiresInHours, revocabile, auditato)
TOKEN=$(curl -s http://localhost:8291/api/v1/auth/login \
  -H 'content-type: application/json' \
  -d '{"tenantId":"<tenant-uuid>","deviceCode":"IPC-01","pin":"1234"}' \
  | jq -r .token)

# PIN condiviso tra operatori → 409 PIN_AMBIGUOUS + lista dei candidati;
# ri-invia con "operatorId" per disambiguare.

2 — Ciclo di vita del lotto: recezione → lavorazione → collaudo

# VERIFY_LOT (ruolo MAGAZZINIERE) — FSM IN_ATTESA → VERIFICATO
curl -X POST http://localhost:8291/api/v1/actions/VERIFY_LOT \
  -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -H "X-Idempotency-Key: $(uuidgen)" \
  -d '{"payload":{"lot_id":"LOT-2026-091","peso_dichiarato":100.0,
        "peso_misurato":100.3}}'

# ACCEPT_LOT — la formula `weight_delta_pct` gira, il constraint `peso_coerente`
# fallisce chiuso oltre ±0.5%: rifiuto, zero commit.
curl -X POST http://localhost:8291/api/v1/actions/ACCEPT_LOT \
  -H "Authorization: Bearer $TOKEN" -H "X-Idempotency-Key: $(uuidgen)" \
  -d '{"payload":{"lot_id":"LOT-2026-091","peso_dichiarato":100.0,
        "peso_misurato":100.3}}'

# Collaudo (ruolo QUALITA) — START_MEASURE poi QC_PASS/QC_FAIL
curl -X POST http://localhost:8291/api/v1/actions/QC_PASS \
  -H "Authorization: Bearer $TOKEN" -H "X-Idempotency-Key: $(uuidgen)" \
  -d '{"payload":{"piece_id":"PZ-001","misura":0.018}}'
# misura > 0.02 → CONSTRAINT_VIOLATED (tolleranza_collaudo).
# Un trigger che il tuo ruolo non possiede → 403. Trigger fuori stato → 409.
// Lo stesso flusso in Node — una funzione async, zero dipendenze
async function closeOperation(base, token, order) {
  const r = await fetch(`${base}/api/v1/actions/CLOSE_OPERATION`, {
    method: 'POST',
    headers: { 'content-type': 'application/json',
               authorization: `Bearer ${token}`,
               'x-idempotency-key': crypto.randomUUID() },
    body: JSON.stringify({ payload: {
      order_id: order, qta: 500, qta_ordinata: 500 } })
  });
  if (r.status === 401) throw new Error('sessione scaduta — rifare login');
  return r.json();
}
// qta > qta_ordinata → la regola `qta_entro_ordine` rifiuta: non puoi mai
// committare più dell'ordinato. La formula `tempo_std` gira per la riga di audit.
# Le query di lettura sono dichiarate nel manifest e indirizzate come trigger
curl -X POST http://localhost:8291/api/v1/actions/LIST_LOTTI \
  -H "Authorization: Bearer $TOKEN" -H "X-Idempotency-Key: $(uuidgen)" \
  -d '{"payload":{"lot_id":"LOT-2026-091"}}' | jq '.rows'

4 — Lato cloud: sessione portale → superficie tenant

# Login portale (cloud) → sessione Bearer
PS=$(curl -s https://cp.axiom-trace.eu/api/v1/portal/login \
  -H 'content-type: application/json' \
  -d '{"email":"qa@customer.com","password":"…","tenant_id":"<uuid>"}' \
  | jq -r .token)

# Uno snapshot: contratto, heartbeat della flotta, metering mensile
curl -s https://cp.axiom-trace.eu/api/v1/tenant/usage \
  -H "Authorization: Bearer $PS" | jq '{plan: .subscription.plan, edges: .edges|length}'

# Ri-verifica la catena di audit hash-chained del tenant — integrità self-service
curl -s https://cp.axiom-trace.eu/api/v1/tenant/audit/verify \
  -H "Authorization: Bearer $PS" | jq '{ok, checked, head}'

# Export SIEM — NDJSON paginato a cursore, gli stessi fatti che la catena verifica
curl -s "https://cp.axiom-trace.eu/api/v1/tenant/audit?limit=1000" \
  -H "Authorization: Bearer $PS" | head -1 | jq '{seq, action, integrity}'
# Equivalente in Python — export audit incrementale per un collector SIEM
import httpx
S = httpx.Client(base_url="https://cp.axiom-trace.eu",
                 headers={"authorization": f"Bearer {token}"})
since = 0
while True:
    rows = S.get("/api/v1/tenant/audit",
                 params={"since_seq": since, "limit": 1000}).json()
    events = rows["events"] if isinstance(rows, dict) else rows
    if not events:
        break
    since = int(events[-1]["seq"])   # seq è una stringa 64-bit — converti
    ship_to_siem(events)

5 — Ingest (canale edge → cloud)

# Il drain outbound del demone edge — idempotente su id evento, ≤1000/batch
curl -X POST https://cp.axiom-trace.eu/api/v1/sync/ingest \
  -H "Authorization: Bearer $EDGE_API_KEY" \
  -H "x-edge-node: edge-01" \
  -H "x-edge-signature: ed25519.<b64-of-raw-body>" \
  -H 'content-type: application/json' \
  -d '{"events":[{"id":"<uuid>","aggregate_type":"BIZ_LOTTI",
        "aggregate_id":"<uuid>","event_type":"INSERT_BIZ_LOTTI",
        "payload":{"lotto":"LOT-2026-091","peso_misurato":100.3},
        "created_at":"2026-09-26T14:01:03Z"}]}'
# → 201 {processed_count, duplicate_count, latest_manifest_version}
#   reinviare lo stesso batch non è un errore, è il percorso di retry progettato.

Dove proseguire