Developer API · v1

Sigillo, verifica, webhook. Integra Veylon nel tuo backend.

API REST minimale per il piano Business. Manda un hash SHA-256, ricevi un sigillo Bitcoin via OpenTimestamps. Quota dedicata. SLA. Webhook delivery in tempo reale.

Autenticazione

Tutte le API REST richiedono un'API key nel formato ufficiale vlk_* (32 byte base64url). Inviare nel header Authorization: Bearer vlk_xxxxxxxx.

Formato legacy non supportato. Le vecchie chiavi veylon_live_* / veylon_test_* ritornano 401 legacy_format_not_supported. Migrare al nuovo formato dal Dev Portal.
curl -H "Authorization: Bearer vlk_xxxxxxxx..." \
     https://api.veylon.app/v1/tag/cert?codice=VLN-TAG-2026-AB23-9KMP

Quota mensile per piano: free 10 / pro 1.000 / business 50.000 / enterprise 500.000. Lo scope di lettura non scala quota. Header standard restituiti: X-RateLimit-Limit, X-RateLimit-Remaining, X-Veylon-Piano, X-Veylon-Api-Version: v1.

API keys self-service

Le API key vlk_* si generano e gestiscono dal Dev Portal interno. Ogni chiave porta uno o piu scope granulari: sigillo:create, tag:write, tag:read, tag:read:any, tag:transfer, admin.

Gestisci le tue API key →

Il valore plain della chiave appare una sola volta: copiarla subito in un secret manager. Il backend persiste solo lo SHA-256 hex. La revoca e immediata (active=false, propagata su tutte le edge function al successivo lookup).

Audit log integrato. Ogni chiamata viene registrata in audit_log con actor_type: api_key, api_key_id, scope richiesti, IP hash, user agent.

Sigillo

POST/v1/sigillo

Crea un sigillo Veylon per un documento. Trasmesso solo l'hash SHA-256 (il documento non lascia il vostro perimetro).

Request body

{
  "sha256": "a3f5b8c7d9e1f2a4...",  // 64 char hex, obbligatorio
  "doc_name": "Contratto Acme 2026", // string max 200
  "anchor_method": "opentimestamps", // opzionale, default opentimestamps
  "metadata": { "modulo": "contratto" } // opzionale, jsonb
}

Response 200

{
  "success": true,
  "atto_id": "uuid",
  "sha256": "a3f5b8c7d9e1f2a4...",
  "ots_status": "pending",
  "verifica_url": "https://veylon.app/verifica.html?h=a3f5b8c7...",
  "created_at": "2026-06-16T20:45:00Z"
}

Esempi

curl -X POST https://api.veylon.app/v1/sigillo \
  -H "Authorization: Bearer veylon_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"sha256":"a3f5b8c7d9e1f2a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2","doc_name":"Contratto Acme"}'
import crypto from "node:crypto";
const buf = await fs.readFile("contratto.pdf");
const sha = crypto.createHash("sha256").update(buf).digest("hex");
const r = await fetch("https://api.veylon.app/v1/sigillo", {
  method: "POST",
  headers: { "Authorization": `Bearer ${process.env.VEYLON_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ sha256: sha, doc_name: "Contratto Acme" }),
});
const atto = await r.json();
console.log(atto.verifica_url);
import hashlib, requests, os
sha = hashlib.sha256(open("contratto.pdf", "rb").read()).hexdigest()
r = requests.post("https://api.veylon.app/v1/sigillo",
  headers={"Authorization": f"Bearer {os.environ['VEYLON_KEY']}"},
  json={"sha256": sha, "doc_name": "Contratto Acme"})
print(r.json()["verifica_url"])

Tag · Emissione singola

POST/v1/tag/emit

Emette un singolo certificato Veylon Tag (DPP UE 2024/1781, batteria Annex XIII, EUDR DDS, tessile Prato 2027, pharma GMP, lusso). 1 sigillo per chiamata.

Request body (estratto)

{
  "produttore_id": "uuid",                  // obbligatorio
  "sha256_dichiarazione": "<64 hex>",       // obbligatorio
  "codice_prodotto": "BAG-001",             // opzionale, max 200
  "lotto": "L-2026-04",                     // opzionale
  "applicazione": "qr_adesivo",             // qr_adesivo | qr_inciso | nfc_ntag424_dna
  "categoria_dpp": "textile_general",       // opzionale, classifica DPP
  "gtin": "08001234567890",                 // opzionale, GS1
  "regulatory_compliance": { "reach": true },
  "visibility_jsonb": { "public": ["origin"] },
  "metadata": {}
}

Response 200

{
  "success": true,
  "atto_id": "uuid",
  "certificato_id": "uuid",
  "codice_pubblico": "VLN-TAG-2026-AB23-9KMP",
  "qr_url": "https://veylon.app/t/VLN-TAG-2026-AB23-9KMP",
  "verifica_url": "...",
  "ots_status": "pending",
  "quota_used": 124, "quota_remaining": 49876, "quota_monthly": 50000,
  "piano": "business"
}

Esempi

curl -X POST https://api.veylon.app/v1/tag/emit \
  -H "Authorization: Bearer $VEYLON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"produttore_id":"...","sha256_dichiarazione":"a3f5...","codice_prodotto":"BAG-001","lotto":"L-2026-04","applicazione":"qr_adesivo"}'
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";

const sha = createHash("sha256").update(await readFile("dichiarazione.pdf")).digest("hex");
const r = await fetch("https://api.veylon.app/v1/tag/emit", {
  method: "POST",
  headers: { "Authorization": `Bearer ${process.env.VEYLON_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    produttore_id: process.env.VEYLON_PRODUTTORE_ID,
    sha256_dichiarazione: sha,
    codice_prodotto: "BAG-001", lotto: "L-2026-04",
    applicazione: "qr_adesivo",
  }),
});
const out = await r.json();
console.log(out.qr_url, "quota rimanente:", out.quota_remaining);
import hashlib, os, requests

sha = hashlib.sha256(open("dichiarazione.pdf", "rb").read()).hexdigest()
r = requests.post(
    "https://api.veylon.app/v1/tag/emit",
    headers={"Authorization": f"Bearer {os.environ['VEYLON_KEY']}"},
    json={
        "produttore_id": os.environ["VEYLON_PRODUTTORE_ID"],
        "sha256_dichiarazione": sha,
        "codice_prodotto": "BAG-001", "lotto": "L-2026-04",
        "applicazione": "qr_adesivo",
    },
    timeout=15,
)
print(r.json()["qr_url"])

Tag · Bulk asincrono

POST/v1/tag/bulk

Enqueue di un lotto fino a 100.000 righe per call (lotti grandi fino a 10M con worker dedicato, contattare [email protected]). Risposta HTTP 202 + job_id. Polling con GET /v1/tag/job-status?job_id=.... Header Idempotency-Key supportato (replay 24h).

Request body

{
  "produttore_id": "uuid",
  "codice_lotto": "LOT-2026-04",
  "webhook_url": "https://acme.example.com/hooks/veylon",
  "righe": [
    { "sha256_dichiarazione": "...", "codice_prodotto": "BAG-001", "lotto": "LOT-2026-04", "applicazione": "qr_adesivo" }
    // ...fino a 100000 oggetti
  ]
}

Response 202

{
  "success": true,
  "job_id": "uuid",
  "status": "queued",
  "righe_richieste": 4523,
  "codice_lotto": "LOT-2026-04",
  "polling_url": "https://veylon.app/functions/v1/api-v1-tag-job-status?job_id=...",
  "eta_sec_stimato": 7,
  "idempotency_key": "lotto-2026-04-retry-1"
}

Esempi

curl -X POST https://api.veylon.app/v1/tag/bulk \
  -H "Authorization: Bearer $VEYLON_KEY" \
  -H "Idempotency-Key: lotto-2026-04-retry-1" \
  -H "Content-Type: application/json" \
  -d @lotto-2026-04.json
const r = await fetch("https://api.veylon.app/v1/tag/bulk", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.VEYLON_KEY}`,
    "Idempotency-Key": "lotto-2026-04-retry-1",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    produttore_id: process.env.VEYLON_PRODUTTORE_ID,
    codice_lotto: "LOT-2026-04",
    webhook_url: "https://acme.example.com/hooks/veylon",
    righe: items.map((it) => ({
      sha256_dichiarazione: it.sha,
      codice_prodotto: it.sku,
      lotto: "LOT-2026-04",
      applicazione: "qr_adesivo",
    })),
  }),
});
const job = await r.json();
console.log("job", job.job_id, "ETA", job.eta_sec_stimato, "s");
import os, requests

r = requests.post(
    "https://api.veylon.app/v1/tag/bulk",
    headers={
        "Authorization": f"Bearer {os.environ['VEYLON_KEY']}",
        "Idempotency-Key": "lotto-2026-04-retry-1",
    },
    json={
        "produttore_id": os.environ["VEYLON_PRODUTTORE_ID"],
        "codice_lotto": "LOT-2026-04",
        "webhook_url": "https://acme.example.com/hooks/veylon",
        "righe": righe,
    },
    timeout=60,
)
print("job", r.json()["job_id"])
OTS Merkle aggregato. I lotti bulk usano un'unica radice Merkle ancorata a Bitcoin via OpenTimestamps invece di N submit separate (vedi _shared/merkle.ts + tag-ots-merkle-finalize-cron). Il certificato di lotto ritorna comunque ots_status per ogni riga, derivato dalla prova Merkle.

Verifica

GET/v1/verifica?sha256={64hex}

Recupera lo stato di un sigillo dato il suo SHA-256. Endpoint pubblico (no auth) per integrazioni di verifica indipendenti (dogana EU, distributori, audit pharma).

Esempio chiamata

curl https://api.veylon.app/v1/verifica?sha256=a3f5b8c7d9e1f2a4...

Response 200

{
  "found": true,
  "sha256": "a3f5b8c7...",
  "doc_name": "Contratto Acme 2026",
  "ots_status": "confirmed",
  "btc_block": 892341,
  "btc_tx": "7f3e5a9c1d4b8f2e...",
  "created_at": "2026-06-14T15:30:00Z",
  "confirmed_at": "2026-06-14T17:42:00Z"
}

Multi-firma (K-S.2)

POST/v1/multi-firma

Crea richiesta firma multi-parte. Veylon invia OTP via email a ogni firmatario, traccia firme, notifica owner al completamento.

{
  "doc_name": "NDA Acme",
  "doc_sha256": "a3f5...",
  "ordine_tipo": "parallelo",  // o "sequenziale"
  "scadenza_hours": 168,
  "firmatari": [
    { "email": "[email protected]", "nome": "Mario Rossi" },
    { "email": "[email protected]", "nome": "Manuel Finelli" }
  ]
}

Webhook outbound

Veylon notifica via HTTPS POST gli eventi importanti al vostro endpoint registrato. Configurare in admin.html → tab Webhook.

Eventi disponibili

atto.creato            // appena ricevuto POST /v1/sigillo
atto.confermato        // OTS Bitcoin block confirmation
firma.completata       // singola firma OTP
multi_firma.completata // tutti firmatari hanno firmato
cert.emesso            // certificato istituzionale rilasciato
tag.trasferito         // proprieta tag prodotto cambiata
tag.bulk.completed     // job bulk REST v1 terminato (manifest ZIP signed_url)
tag.bulk.failed        // job bulk REST v1 fallito
tag.cert.transferred   // /v1/tag/transfer chiuso o pending
tag.cert.revoked       // /v1/tag/revoke emesso

Payload

{
  "event": "atto.confermato",
  "timestamp": "2026-06-16T21:00:00Z",
  "data": {
    "atto_id": "uuid",
    "sha256": "a3f5...",
    "btc_block": 892341,
    "btc_tx": "7f3e..."
  },
  "signature": "sha256=hmac..."  // HMAC con webhook_secret
}
HMAC mandatorio. Tutti i payload webhook sono firmati con HMAC-SHA256. Validare la firma con il webhook_secret generato in admin prima di processare.

Errori

Tutti gli errori usano status HTTP standard + body JSON con error e opzionalmente detail.

400 Bad Request       // input malformato
401 Unauthorized      // API key mancante o invalida
402 Payment Required  // quota esaurita
403 Forbidden         // piano non Business
404 Not Found         // risorsa inesistente
422 Unprocessable     // validazione semantica
429 Too Many Requests // rate limit (vedi sotto)
500 Internal          // errore Veylon (riprova + apri ticket)
502 Bad Gateway       // OTS calendari irraggiungibili
503 Service Unavailable // manutenzione pianificata

Rate limit + Quota

Quota mensile applicata SOLO agli scope di scrittura (*:write, *:transfer, sigillo:create). Gli scope di lettura non scalano quota.

Piano Quota / mese SLA
free10
pro1.00099,5%
business50.00099,9%
enterprise500.00099,95%

Quota esaurita → 402 Payment Required con upgrade_url nella risposta. Reset al primo hit del nuovo mese UTC (CAS atomic, vedi _shared/api-bearer.ts).

X-RateLimit-Limit: 50000
X-RateLimit-Remaining: 49876
X-Veylon-Piano: business
X-Veylon-Api-Version: v1

Header speciale su replay idempotente del bulk: X-Veylon-Idempotent-Replay: 1.

SLA

Piano Business include SLA:

  • Uptime mensile 99.9% (monitorato su system-status.html)
  • Latenza P95 endpoint sigillo: < 800ms
  • OTS conferma media: 1-3 ore (dipende blockchain Bitcoin)
  • Risposta supporto: < 4 ore business day
  • Bug critici: < 24 ore