API Reference · v1

REST API pubblica

Integra Allega con il tuo stack: gestionale, CRM, automazioni Zapier/n8n/Make, script personalizzati. Tutti gli endpoint richiedono una API key generata dalla dashboard.

Versione: 1.0Base URL: https://app.allega.it/api/v1Auth: Bearer tokenFormato: JSON

1. Introduzione

L'API REST pubblica di Allega espone le risorse principali del sistema (pratiche, clienti, file) tramite endpoint HTTPS sotto il prefisso /api/v1. È pensata per integrazioni server-to-server: gestionali contabili, CRM, automazioni no-code, script di importazione dati.

Tutti gli endpoint:

Le risposte includono un campo data per i dataset listati e una metadata di pagination. Gli errori usano l'envelope { error: { code, message } } .

2. Autenticazione

Ogni richiesta deve includere l'header Authorization: Bearer <chiave>. La chiave inizia con il prefisso ak_live_ seguito da 43 caratteri base64url (~256 bit di entropia).

curl -H "Authorization: Bearer ak_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  https://app.allega.it/api/v1/me

Generazione e gestione

  1. Vai a Impostazioni → API keys nella dashboard.
  2. Clicca Nuova chiave: scegli nome, scope e scadenza.
  3. La chiave viene mostrata una sola volta. Copiala e archiviala in un password manager o nei segreti del CI. Non c'è recupero.
  4. Per revocare una chiave: Impostazioni → API keys → Revoca. La revoca è immediata su tutti i nodi.

Storage server-side

Conserviamo solo l'hash SHA-256 della chiave. Il valore in chiaro non transita mai nei nostri log e non è recuperabile nemmeno dal team interno. La rotazione è raccomandata ogni 90 giorni per chiavi machine-to-machine.

Errori di autenticazione

HTTPcodeQuando
401missing_authorizationHeader Authorization assente o non Bearer
401malformed_tokenToken non inizia con ak_live_ o lunghezza invalida
401invalid_tokenHash non trovato — chiave inesistente
401revoked_tokenChiave revocata via dashboard
401expired_tokenSuperata la data di expiresAt
403insufficient_scopeLa chiave non possiede lo scope richiesto
429rate_limitedSoglia superata (vedi sezione Rate limit)

3. Scope (permessi)

Le chiavi sono scopate: ogni chiave dichiara cosa può fare. Una chiave senza il giusto scope riceve 403 insufficient_scope. Scegli lo scope minimo necessario per la tua integrazione (principio di least-privilege).

ScopeConcede
read:practicesLista + dettaglio pratiche, items checklist, share URL
write:practices(Riservato a versioni future) creazione, invio, completamento
read:clientsLista + dettaglio clienti (anagrafica, tag, canale preferito)
write:clientsPOST /api/v1/clients per creare nuovi clienti
read:filesURL firmato di download (scadenza 5 minuti) per i file caricati

4. Rate limiting

Limite per chiave: 120 richieste/minuto (sliding window). Superato il limite ricevi 429 rate_limited. Il limite è applicato server-side via Upstash Redis ed è indipendente dall'IP chiamante.

In caso di errore Redis (infrastruttura) il limite fail-open: l'API risponde comunque, ma scriviamo l'evento nel log interno. Non c'è SLA hard sul rate limiter — è una protezione anti-abuso, non un quality-of-service contract.

Per workload più alti contattaci: possiamo concordare limiti dedicati nel piano Pro o un bucket separato.

5. Versioning & deprecation

La versione è nel path: /api/v1/.... Manteniamo la retrocompatibilità all'interno della stessa versione major. Se introduciamo breaking changes pubblicheremo /api/v2 e manterremo v1 attiva per almeno 12 mesi dalla data di rilascio di v2, con un avviso nella dashboard e via email.

Sono considerate compatibili:

Sono considerate breaking:

6. Errori

Tutti gli errori usano lo stesso envelope JSON, con codici stabili sotto error.code:

{
  "error": {
    "code": "insufficient_scope",
    "message": "Missing required scope: write:clients"
  }
}

Il campo message è in inglese, sicuro da mostrare per debug interno ma non destinato all'utente finale (può cambiare). Discrimina sempre sul code stabile.

Codici comuni

HTTPcodeSignificato
400invalid_jsonBody non è JSON valido
400invalid_idUUID nel path malformato
400invalid_statusFiltro status sconosciuto
400invalid_emailEmail malformata
400invalid_nameNome client fuori range 2..200 char
404not_foundRisorsa inesistente o non appartiene allo studio
409email_conflictEmail duplicata per un altro client dello studio
500internal_errorEccezione non gestita — segnala a support

7. Paginazione

Tutti gli endpoint GET /api/v1/... che restituiscono liste supportano paginazione con offset/limit:

La risposta include un blocco pagination con il totale assoluto:

{
  "data": [ ... ],
  "pagination": { "limit": 50, "offset": 0, "total": 287 }
}

Per dataset grandi raccomandiamo di iterare con offset += limitfinché offset < total. L'ordinamento è updated_at DESC stabile.

8. Endpoints

GET/api/v1/meno scope

Probe di connettività. Restituisce lo studio autenticato e gli scope attivi della chiave. Nessuno scope richiesto.

Esempio

curl -H "Authorization: Bearer $ALLEGA_KEY" https://app.allega.it/api/v1/me

Risposta (200)

{
  "studio": {
    "id": "f8a1...",
    "name": "Studio Rossi & Associati",
    "slug": "studio-rossi",
    "plan": "studio",
    "timezone": "Europe/Rome",
    "defaultLocale": "it"
  },
  "apiKey": {
    "id": "9d3e...",
    "scopes": ["read:practices", "read:clients"]
  }
}
GET/api/v1/practicesscope: read:practices

Lista pratiche dello studio. Filtri opzionali per status, clientId e ricerca testuale sul nome.

Query parameters

  • status draft | active | in_review | needs_action | completed | expired | archived
  • clientId UUID — restringe a una specifica anagrafica
  • q Ricerca case-insensitive sul nome pratica
  • limit 1..100, default 50
  • offset 0..n, default 0

Esempio

curl -H "Authorization: Bearer $ALLEGA_KEY" \
  "https://app.allega.it/api/v1/practices?status=active&limit=20"

Risposta (200)

{
  "data": [
    {
      "id": "7c1e...",
      "name": "Bilancio 2025",
      "description": null,
      "status": "active",
      "client": {
        "id": "a4f2...",
        "name": "Mario Rossi",
        "email": "mario@example.com"
      },
      "shareUrl": "https://app.allega.it/p/AbCdEf1234...",
      "shareExpiresAt": "2026-08-01T00:00:00.000Z",
      "shareOpenedAt": "2026-05-03T14:22:11.000Z",
      "dueDate": "2026-06-30T00:00:00.000Z",
      "completedAt": null,
      "createdAt": "2026-05-01T09:00:00.000Z",
      "updatedAt": "2026-05-03T14:22:11.000Z"
    }
  ],
  "pagination": { "limit": 20, "offset": 0, "total": 87 }
}
GET/api/v1/practices/{id}scope: read:practices

Dettaglio singola pratica con la lista completa di items checklist (documenti richiesti).

Esempio

curl -H "Authorization: Bearer $ALLEGA_KEY" \
  https://app.allega.it/api/v1/practices/7c1e3a4b-...

Risposta (200)

{
  "id": "7c1e...",
  "name": "Bilancio 2025",
  "status": "active",
  "client": { "id": "a4f2...", "name": "Mario Rossi", "email": "..." },
  "shareUrl": "https://app.allega.it/p/AbCdEf1234...",
  "shareSubmittedAt": null,
  "items": [
    {
      "id": "1aa2...",
      "name": "CCIAA visura aggiornata",
      "category": "anagrafica",
      "required": true,
      "multiFile": false,
      "acceptedFormats": ["pdf"],
      "maxFileSizeMb": 25,
      "status": "approved",
      "approvedAt": "2026-05-02T10:11:00.000Z",
      "rejectedAt": null,
      "order": 1
    }
  ]
}
POST/api/v1/practicesscope: write:practices

Crea una pratica a partire da un preset. La pratica viene creata in stato draft con shareToken già generato. Per inviare l'email di invito al cliente, chiama POST /api/v1/practices/{id}/send.

Esempio

curl -X POST \
  -H "Authorization: Bearer $ALLEGA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "a4f2-...",
    "presetId": "55ee-...",
    "name": "Bilancio 2025",
    "dueDate": "2026-06-30T23:59:00.000Z"
  }' \
  https://app.allega.it/api/v1/practices

Risposta (200)

{
  "id": "7c1e...",
  "name": "Bilancio 2025",
  "status": "draft",
  "clientId": "a4f2-...",
  "shareUrl": "https://app.allega.it/p/AbCdEf1234...",
  "shareExpiresAt": "2026-06-30T23:59:00.000Z",
  "dueDate": "2026-06-30T23:59:00.000Z",
  "createdAt": "2026-05-04T..."
}
POST/api/v1/practices/{id}/sendscope: write:practices

Attiva una pratica draft (status='active') ed emette l'evento practice/invited che invia l'email di invito al cliente. Idempotente: chiamare due volte produce lo stesso risultato.

Esempio

curl -X POST \
  -H "Authorization: Bearer $ALLEGA_KEY" \
  https://app.allega.it/api/v1/practices/7c1e.../send

Risposta (200)

{
  "id": "7c1e...",
  "status": "active",
  "shareUrl": "https://app.allega.it/p/AbCdEf1234..."
}
GET/api/v1/clientsscope: read:clients

Lista clienti dell'anagrafica. Filtri q (nome o email), includeArchived.

Query parameters

  • q Ricerca su name e email (case-insensitive)
  • includeArchived 1 per includere archiviati (default esclusi)
  • limit 1..100, default 50
  • offset 0..n, default 0

Esempio

curl -H "Authorization: Bearer $ALLEGA_KEY" \
  "https://app.allega.it/api/v1/clients?q=rossi"

Risposta (200)

{
  "data": [
    {
      "id": "a4f2...",
      "name": "Mario Rossi",
      "email": "mario@example.com",
      "phone": "+39 333 1234567",
      "fiscalCode": "RSSMRA80A01H501Z",
      "tags": ["azienda", "iva"],
      "preferredChannel": "email",
      "archived": false,
      "createdAt": "2026-01-15T...",
      "updatedAt": "2026-05-03T..."
    }
  ],
  "pagination": { "limit": 50, "offset": 0, "total": 12 }
}
POST/api/v1/clientsscope: write:clients

Crea un cliente. Validazione: nome 2..200, email opzionale RFC, fiscalCode opzionale (uppercased automaticamente). Restituisce 409 email_conflict se l'email è già usata da un altro cliente attivo dello stesso studio.

Esempio

curl -X POST \
  -H "Authorization: Bearer $ALLEGA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Anna Bianchi",
    "email": "anna@example.com",
    "phone": "+39 348 7654321",
    "fiscalCode": "BNCNNA85B41F205X",
    "tags": ["azienda"],
    "preferredChannel": "whatsapp"
  }' \
  https://app.allega.it/api/v1/clients

Risposta (200)

{
  "id": "f1e2...",
  "name": "Anna Bianchi",
  "email": "anna@example.com",
  "phone": "+39 348 7654321",
  "fiscalCode": "BNCNNA85B41F205X",
  "tags": ["azienda"],
  "preferredChannel": "whatsapp",
  "archived": false,
  "createdAt": "2026-05-04T...",
  "updatedAt": "2026-05-04T..."
}
GET/api/v1/clients/{id}scope: read:clients

Dettaglio singolo cliente con campo pinned aggiuntivo.

Esempio

curl -H "Authorization: Bearer $ALLEGA_KEY" \
  https://app.allega.it/api/v1/clients/a4f2...
PATCH/api/v1/clients/{id}scope: write:clients

Aggiorna un cliente. Tutti i campi sono opzionali; passa solo quelli da modificare. Restituisce 409 email_conflict se la nuova email è già in uso da un altro cliente attivo.

Esempio

curl -X PATCH \
  -H "Authorization: Bearer $ALLEGA_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+39 333 9999999", "tags": ["vip", "azienda"] }' \
  https://app.allega.it/api/v1/clients/a4f2...

Risposta (200)

{
  "id": "a4f2...",
  "name": "Mario Rossi",
  "email": "mario@example.com",
  "phone": "+39 333 9999999",
  "tags": ["vip", "azienda"],
  "preferredChannel": "email",
  "archived": false,
  "updatedAt": "2026-05-04T..."
}
GET/api/v1/files/{id}/downloadscope: read:files

Restituisce un URL firmato di download R2 valido per 5 minuti. File in quarantena antivirus restituiscono 403 quarantined.

Esempio

curl -H "Authorization: Bearer $ALLEGA_KEY" \
  https://app.allega.it/api/v1/files/d3c4.../download

Risposta (200)

{
  "id": "d3c4...",
  "originalName": "F24_2026Q1.pdf",
  "mimeType": "application/pdf",
  "sizeBytes": 245128,
  "sha256": "9f86d081884c7d659a2feaa0...",
  "scanStatus": "clean",
  "uploadedAt": "2026-05-03T...",
  "downloadUrl": "https://r2.allega.it/...?X-Amz-Signature=...",
  "downloadUrlExpiresInSec": 300
}

9. Webhooks (in arrivo)

I webhook in uscita per eventi practice.completed, file.uploaded, file.approved, file.rejected sono in roadmap Q3 2026. Per ora puoi pollare /api/v1/practices con un cursor su updatedAt.

Vuoi accesso anticipato? Scrivi a api@allega.it.

10. Esempi completi

Node.js (fetch nativo)

const KEY = process.env.ALLEGA_API_KEY;
const BASE = 'https://app.allega.it/api/v1';

async function listActivePractices() {
  const res = await fetch(`${BASE}/practices?status=active&limit=100`, {
    headers: { Authorization: `Bearer ${KEY}` },
  });
  if (!res.ok) {
    const err = await res.json();
    throw new Error(`${err.error.code}: ${err.error.message}`);
  }
  const { data, pagination } = await res.json();
  console.log(`Found ${pagination.total} active practices`);
  return data;
}

listActivePractices().then((rows) => console.table(rows));

Python (requests)

import os, requests

KEY = os.environ['ALLEGA_API_KEY']
BASE = 'https://app.allega.it/api/v1'

r = requests.get(
    f'{BASE}/clients',
    headers={'Authorization': f'Bearer {KEY}'},
    params={'q': 'rossi', 'limit': 50},
    timeout=10,
)
r.raise_for_status()
for c in r.json()['data']:
    print(c['name'], c['email'])

Zapier / n8n

Usa il nodo HTTP Request:

11. Security best practice

12. Changelog