Guida

API: creare lead e avviare chiamate

Guida alle API di Aptiva per lead e chiamate: creazione e aggiornamento dei contatti con i campi liberi additionalInfo, avvio della chiamata con la cadenza automatica dei richiami, lettura di esito, trascrizione e durate, con l'esempio completo dal form del sito all'esito.

Redazione Aptiva24 min di lettura

In breve

  • Un lead si crea con POST /leads/create indicando almeno il telefono: se un lead con lo stesso numero esiste già, viene aggiornato e la risposta è comunque 201 con il suo id. Il telefono viene validato e normalizzato nel formato internazionale.
  • I campi liberi additionalInfo e leadMetadata si aggiornano per fusione: invii solo le chiavi che cambiano, una chiave a null la cancella. L'aggiornamento parziale con PATCH tocca solo i campi presenti nel JSON.
  • Una chiamata si avvia con POST /calls/start e il body leadId, agentId e force. La risposta 204 significa «richiesta accettata»: la chiamata viene eseguita subito, oppure programmata secondo la cadenza dei richiami se il contatto è già stato chiamato.
  • Con la cadenza dei richiami Aptiva riprova da sola con i lead che non rispondono: fino a cinque chiamate in tutto, la prima subito e a qualunque ora, poi quattro richiami distribuiti su più giorni. Oltre il quinto tentativo una richiesta non forzata risponde 409.
  • La policy dei richiami dice, per ogni agente in uscita, chi governa i tentativi: con all (predefinita) Aptiva, con ai_recall_only e none il tuo sistema, e allora ogni richiesta parte subito. Si legge e si cambia con GET e PUT /agents/ID/recall-policy.

Con le API di Aptiva un tuo sistema può creare un lead (un contatto con almeno il numero di telefono) e far partire la chiamata dell'agente vocale AI verso quel contatto, poi leggere l'esito: come è andata, cosa si sono detti, quanto è durata. Sono due richieste HTTP: POST /leads/create, che restituisce l'id del lead, e POST /calls/start, che avvia la chiamata. È il flusso tipico di un form del sito o di un CRM che vuole richiamare chi ha appena lasciato i propri dati, mentre l'interesse è ancora caldo.

Questa guida spiega i due gruppi di endpoint con il loro comportamento reale, la cadenza automatica dei richiami (una funzione da conoscere per non stupirsi di un 204 senza chiamata immediata), la policy dei richiami con cui decidi se quella cadenza la governa Aptiva o il tuo sistema, i codici di errore, e chiude con l'esempio completo «form del sito → lead → chiamata → esito». Le convenzioni comuni (chiave, header X-API-Key, formato degli errori, paginazione) sono nella guida ai primi passi. Tutti i percorsi vanno aggiunti all'indirizzo base https://api.aptiva.cloud/external/api.

Creare un lead: upsert per telefono#

Un lead si crea con POST /leads/create e un body JSON in cui l'unico campo obbligatorio è phone. La risposta è 201 con l'identificativo del lead:

curl -sS -X POST "https://api.aptiva.cloud/external/api/leads/create" \
  -H "X-API-Key: $APTIVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "<IL_TUO_NUMERO_DI_PROVA>",
    "name": "Mario",
    "lastName": "Rossi",
    "email": "mario.rossi@example.com",
    "leadCompanyName": "Rossi Srl",
    "additionalInfo": {
      "origine": "form preventivo",
      "prodotto": "impianto fotovoltaico 6 kW",
      "note": "preferisce essere richiamato al pomeriggio"
    },
    "leadMetadata": {
      "externalLeadId": "crm-88213"
    }
  }'
{ "id": 1285 }

La creazione è un upsert per telefono: la chiave di unicità è il numero normalizzato, all'interno della tua azienda. Se esiste già un lead con quel numero, non viene creato un duplicato: i campi che invii sovrascrivono quelli del lead esistente, i campi che non invii restano com'erano, e la risposta è comunque 201 con l'id del lead esistente. Il tuo codice non deve quindi distinguere fra «creato» e «aggiornato»: salva l'id e prosegui.

I campi del body:

CampoTipoObbligatorioNote
phonestringasìValidato e normalizzato (vedi sotto)
name, lastNamestringanoNome e cognome
emailstringanoDeve essere un indirizzo email valido
leadCompanyNamestringanoAzienda del contatto
additionalInfooggetto JSON liberonoTutto ciò che vuoi che l'agente sappia o che ti serve ritrovare (vedi sotto)
leadMetadataoggettonoIdentificativi di integrazione: externalLeadId (l'id del contatto nel tuo sistema). Le chiavi ammesse sono solo quelle previste dallo schema: una chiave diversa risponde 422. In lettura la chiave torna come external_lead_id (vedi sotto)

Come viene validato e normalizzato il telefono#

Il numero di telefono viene accettato in qualunque scrittura leggibile (333 123 4567, +39 333 1234567, 0039 333 1234567, 333-1234567) e, se manca il prefisso internazionale, è considerato italiano. Viene poi verificato che sia un numero reale del piano di numerazione (non basta che siano cifre) e salvato nel formato internazionale E.164, per esempio +393331234567: è così che lo ritroverai in ogni risposta.

Un numero non valido fa fallire la richiesta con 422 e codice validation_error; nei details trovi phone_unparsable (non è interpretabile come numero) oppure phone_invalid (è scritto come un numero ma non esiste nel piano di numerazione, per esempio ha una cifra in più). Un buon form del sito valida il telefono prima di inviarlo, ma questo controllo è la rete di sicurezza che evita di far partire chiamate verso numeri inesistenti.

Se il numero è valido ma non si può chiamare, perché estero o a sovrapprezzo, la creazione di un lead nuovo o la modifica del telefono risponde 422 con phone_not_callable nei details: si chiamano solo fissi, cellulari, VoIP e numeri verdi italiani. Un lead che ha già quel numero, per esempio perché creato da una chiamata in entrata dall'estero, resta aggiornabile negli altri campi; avviare una chiamata verso di lui risponde invece 422 con CALL_DESTINATION_NOT_ALLOWED.

additionalInfo e leadMetadata: aggiornamento per fusione#

additionalInfo è un oggetto JSON libero in cui salvi il contesto del contatto: da dove arriva, cosa ha chiesto, note utili. L'agente vocale AI lo riceve come contesto quando chiama il lead (o quando il lead lo chiama), quindi è il modo per dirgli di cosa parlare («ha chiesto un preventivo per un impianto da 6 kW»); su richiesta possiamo limitare le chiavi che l'agente vede. Lo ritrovi nel dettaglio del lead e della chiamata.

Sia additionalInfo sia leadMetadata si aggiornano con la semantica del merge patch (JSON Merge Patch, RFC 7396):

  • le chiavi che invii vengono aggiunte o sovrascritte;
  • le chiavi che non invii restano;
  • una chiave con valore null viene cancellata;
  • gli oggetti annidati vengono fusi ricorsivamente con le stesse regole;
  • inviare l'intero campo a null ("additionalInfo": null) svuota tutto.

Un esempio: il lead ha additionalInfo uguale a {"origine": "form", "prodotto": "fotovoltaico", "note": "pomeriggio"}. Inviando, in una creazione o in un aggiornamento, {"prodotto": "fotovoltaico + accumulo", "note": null, "budget": "15000"}, il risultato è {"origine": "form", "prodotto": "fotovoltaico + accumulo", "budget": "15000"}: origine è rimasto, prodotto è cambiato, note è sparito, budget è stato aggiunto.

Aggiornare un lead: PATCH e semantica dei null#

Un lead esistente si aggiorna con PATCH /leads/update/ID e un body con solo i campi da cambiare. La risposta è 204 senza corpo.

curl -sS -X PATCH "https://api.aptiva.cloud/external/api/leads/update/1285" \
  -H "X-API-Key: $APTIVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "leadCompanyName": null,
    "additionalInfo": { "esito_commerciale": "preventivo inviato", "note": null }
  }'

Le regole per i valori:

CampoOmessoInviato con un valoreInviato a null
email, name, lastName, leadCompanyNameResta com'èSostituitoAzzerato
phoneResta com'èSostituito (validato e normalizzato)Ignorato: il telefono non può essere azzerato
additionalInfo, leadMetadataResta com'èFuso con il merge patchSvuotato

Se cambi phone, il lead cambia numero. Non usare un numero già presente su un altro lead della tua azienda: il telefono è unico per azienda e in quel caso la richiesta oggi fallisce con un errore del server (500, internal_error), non con un errore descrittivo. Altri errori: 404 con not_found se il lead non esiste, 403 con forbidden se appartiene a un'altra azienda.

Leggere i lead: elenco e dettaglio#

L'elenco dei lead è GET /leads, paginato e ordinato con pageNumber, pageSize, orderBy e isAscending come descritto nella guida ai primi passi; il corpo è un array di lead in forma compatta e i metadati sono nell'header X-Pagination.

[
  {
    "id": 1285,
    "email": "mario.rossi@example.com",
    "name": "Mario",
    "lastName": "Rossi",
    "phone": "+393331234567",
    "leadCompanyName": "Rossi Srl"
  }
]

Il dettaglio è GET /leads/ID e aggiunge createdAt, additionalInfo e leadMetadata:

{
  "id": 1285,
  "email": "mario.rossi@example.com",
  "name": "Mario",
  "lastName": "Rossi",
  "phone": "+393331234567",
  "leadCompanyName": "Rossi Srl",
  "createdAt": "2026-09-11T09:32:10.482Z",
  "additionalInfo": {
    "origine": "form preventivo",
    "prodotto": "impianto fotovoltaico 6 kW",
    "note": "preferisce essere richiamato al pomeriggio"
  },
  "leadMetadata": {
    "external_lead_id": "crm-88213"
  }
}

Una particolarità di leadMetadata: in scrittura la chiave si chiama externalLeadId, in lettura torna in snake_case, external_lead_id. Vale anche per il lead incluso nel dettaglio di una chiamata.

Non esiste una ricerca per telefono nell'elenco: GET /leads accetta solo i parametri di paginazione. Se ti serve l'id di un lead a partire dal numero, puoi richiamare POST /leads/create con il solo phone: se il lead esiste ottieni il suo id senza modificarne i dati, ma se non esiste viene creato. Per non creare lead per sbaglio, la via più solida è salvare l'id nel tuo sistema al momento della creazione, accanto al contatto.

Avviare una chiamata: leadId, agentId e force#

Una chiamata in uscita si avvia con POST /calls/start e un body con tre campi:

curl -sS -X POST "https://api.aptiva.cloud/external/api/calls/start" \
  -H "X-API-Key: $APTIVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "leadId": 1285, "agentId": 42, "force": false }'
CampoTipoObbligatorioSignificato
leadIdintero positivosìIl lead da chiamare (dalla risposta di POST /leads/create)
agentIdintero positivosìL'agente che chiama: deve essere un agente per chiamate in uscita, attivo, con un numero attivo; l'id è accanto al nome nel pannello
forcebooleanono, predefinito falsetrue fa partire la chiamata subito anche se il lead è già stato chiamato (vedi la cadenza dei richiami)

La risposta è 204 senza corpo e significa «richiesta accettata»: la chiamata è stata messa in coda per l'esecuzione immediata, oppure programmata secondo la cadenza descritta nella sezione seguente. Il 204 non significa che il telefono sta già squillando: l'esito si legge poi con GET /calls.

Gli errori all'avvio#

StatocodeQuandoCosa fare
404not_foundLead o agente inesistenti (il detail dice quale)Verifica gli id
403forbiddenIl lead appartiene a un'altra aziendaVerifica il leadId
422OUTBOUND_CONFIG_REQUIREDL'agente non è un agente per chiamate in uscitaUsa l'id di un agente in uscita; se ne hai solo uno in entrata, creane uno in uscita dal pannello
422AGENT_PHONE_NUMBER_NOT_FOUNDL'agente non ha un numero di telefono associatoAssegna un numero all'agente dal pannello
422CALL_DESTINATION_NOT_ALLOWEDIl numero del lead non è chiamabile: si chiamano solo fissi, cellulari, VoIP e numeri verdi italiani, niente numeri esteri o a sovrapprezzoVerifica il numero del lead
409AGENT_DISABLEDL'agente è disabilitato oppure il suo numero non è attivoRiattiva l'agente nel pannello; se il numero non è attivo, scrivici
409CALL_RETRY_LIMIT_REACHEDIl lead ha già ricevuto i cinque tentativi della cadenza e la richiesta ha force a falseNon riprovare: usa force a true se la chiamata serve davvero (vedi la cadenza dei richiami)
422validation_errorBody non valido (campo sconosciuto, id non positivo)Leggi details

Il credito non blocca l'avvio: se al momento della chiamata il credito è insufficiente, la chiamata viene registrata con outcome 550 (credito insufficiente) e non viene effettuata, né conta nella cadenza dei richiami. Conviene controllare periodicamente gli esiti e aggiungere crediti, o tenere attivo il rinnovo automatico, prima che accada (Prezzi).

La cadenza dei richiami: una funzione, non un limite#

La cadenza dei richiami serve a richiamare i lead che non rispondono: limita a cinque i tentativi automatici verso la stessa persona e li distribuisce con tempi fatti apposta per trovarla, senza che tu debba scrivere alcuna logica di ripianificazione. Il perché di questi tempi, spiegato per chi si occupa di marketing e vendite, è nella guida su quante volte e quando Aptiva richiama un lead che non risponde.

La cadenza vale per gli agenti con la policy dei richiami all, che è quella predefinita. Con le altre due policy la cadenza la governa il tuo sistema: lo spiega la sezione successiva.

La sequenza è sempre la stessa e dipende da quante chiamate il lead ha già ricevuto. Il ritardo si calcola dal momento in cui il richiamo viene programmato: la fine della chiamata precedente nella catena automatica, l'arrivo della richiesta per POST /calls/start.

TentativoIl lead ha giàQuando parte
1°nessuna chiamataSubito, a qualunque ora
2°1 chiamata5 minuti dopo
3°2 chiamate25 minuti dopo
4°3 chiamateIl giorno dopo alle 13:00
5°4 chiamateIl giorno dopo alle 18:00, contato dal quarto tentativo
oltre il 5°5 o più chiamateNessuna chiamata: la richiesta risponde 409

Nella catena automatica, quindi, il quinto tentativo cade due giorni dopo il terzo: prima chiamata lunedì alle 10:00 senza risposta, seconda verso le 10:05, terza verso le 10:30, quarta martedì alle 13:00, quinta mercoledì alle 18:00.

Il conteggio si basa sulle chiamate già registrate per quel lead, comprese quelle in corso, escluse quelle con outcome 500 (fallita per errore tecnico) e 550 (credito insufficiente). Una chiamata fallita non consuma quindi mai un tentativo.

L'esito 500 non garantisce però un richiamo. Dipende da dove nasce l'errore:

  • l'operatore telefonico rifiuta la chiamata prima dello squillo, per esempio per canali esauriti: Aptiva compone il numero fino a tre volte in circa un minuto, sulla stessa chiamata. Se il rifiuto continua, la chiamata si chiude con 500 e nessun richiamo viene programmato;
  • un guasto della piattaforma durante la composizione: l'avvio viene ripetuto da solo più volte, a intervalli crescenti, con qualunque policy dei richiami. Ogni ripetizione fallita lascia una chiamata con 500;
  • l'errore viene segnalato a chiamata avviata: con la policy all la cadenza programma il tentativo successivo nello slot che sarebbe toccato alla chiamata fallita, ma solo se il lead ha già almeno una chiamata valida. Se a fallire è la prima, nessun richiamo viene programmato.

In pratica: dopo un 500 guarda recallAt. Se è null e non compaiono nuove chiamate verso il lead, tocca al tuo sistema riavviare la chiamata con POST /calls/start, che con conteggio zero parte subito.

I ritardi in minuti (5 e 25) rispettano la fascia dalle 9:00 alle 21:00 (ora italiana), con una regola calcolata sull'ora in cui il richiamo viene programmato, non sull'ora risultante: se sono già passate le 21:00, il richiamo va alle 9:00 del giorno dopo più il ritardo; se non sono ancora le 9:00, alle 9:00 dello stesso giorno più il ritardo; altrimenti parte dopo il ritardo, anche se l'ora risultante supera le 21:00 (una richiesta alle 20:50 con ritardo di 25 minuti viene programmata alle 21:15). Gli orari fissi delle 13:00 e delle 18:00 non hanno bisogno del controllo. La fascia non si applica alla prima chiamata a un lead né alle chiamate con force a true, che partono subito a qualunque ora. È voluto: un contatto che ha appena compilato un form è nel momento di massimo interesse, e va richiamato in quel momento. Se per il tuo caso d'uso preferisci non chiamare in certi orari, decidi tu quando avviare la chiamata.

La cadenza entra in gioco in tre modi:

  1. In automatico, dopo una chiamata senza risposta. Se una chiamata in uscita finisce con outcome 101 (occupato), 102 (nessuna risposta) o 103 (segreteria), Aptiva programma da sola il tentativo successivo secondo la tabella. Per 500 valgono i casi descritti sopra. Nel dettaglio della chiamata il campo recallAt indica quando partirà. Non devi richiamare POST /calls/start: se lo fai, aggiungi un tentativo a quelli già programmati.
  2. Quando avvii tu una chiamata con force a false (il valore predefinito) verso un lead già chiamato: la richiesta non parte subito ma viene programmata nello slot corrispondente al numero di chiamate già fatte. È il comportamento giusto per un CRM che «rimette in coda» un contatto senza preoccuparsi di quando chiamarlo.
  3. Quando il richiamo lo decide l'analisi della chiamata. Per le chiamate in uscita in cui qualcuno ha risposto (outcome 200, 201, 300, 301 o 302), l'analisi post-chiamata può fissare recallAt. Lo fa in due casi: quando la persona chiede di essere richiamata («richiamatemi domani alle 11») e quando la chiamata ha avuto risposta senza una conversazione utile. La chiamata viene programmata all'ora fissata dall'analisi, almeno dieci minuti più tardi, senza il controllo del conteggio né della fascia oraria. Se invece la persona ha chiesto di parlare con un umano (callAnalysis.humanCallbackRequest presente), non viene programmato alcun richiamo automatico: la palla passa a te.

Con force a true la chiamata viene messa in coda subito, qualunque sia lo storico del lead: è la scelta giusta quando è il contatto stesso ad aver chiesto di essere richiamato ora (un form appena inviato, un pulsante «richiamami»). Dal pannello di Aptiva la chiamata manuale è sempre forzata; via API la scelta è tua. Anche dopo una chiamata forzata senza risposta, i tentativi successivi vengono programmati in automatico.

Due punti da dichiarare con chiarezza. Primo: il limite dei cinque tentativi vale per la cadenza automatica e per POST /calls/start con force a false; non vale per force a true né per i richiami fissati dall'analisi post-chiamata. Secondo: oltre il quinto tentativo POST /calls/start con force a false risponde 409 con codice CALL_RETRY_LIMIT_REACHED, e nessuna chiamata viene creata. Il 204 significa quindi sempre che la richiesta è stata accettata. Per richiamare un contatto che ha già esaurito i tentativi, usa force a true.

La policy dei richiami: chi governa i tentativi#

La policy dei richiami è l'impostazione di un agente in uscita che dice quali chiamate Aptiva avvia da sola dopo una sua chiamata. Serve a chi governa la cadenza dal proprio CRM e vuole che ogni chiamata nasca da una richiesta esplicita. I valori sono tre:

recallPolicyTentativi dopo una mancata rispostaRichiami fissati dall'analisiPOST /calls/start con force a false verso un lead già chiamato
all (predefinita)Sì, con la cadenzaSìSegue la cadenza e il limite di cinque tentativi
ai_recall_onlyNoSìParte subito, senza limite di tentativi
noneNoNoParte subito, senza limite di tentativi

Con ai_recall_only e none la cadenza la governa il tuo sistema. Dopo una chiamata senza risposta recallAt resta null e nessun tentativo viene programmato. Ogni tua richiesta parte subito, come con force a true, e il limite dei cinque tentativi non si applica: a proteggere il contatto dall'insistenza è il tuo sistema. Con none l'analisi non fissa richiami: se la persona chiede di essere richiamata, lo trovi nel riepilogo e in callAnalysis.humanCallbackRequest, da gestire a mano.

La policy si legge con GET /agents/ID/recall-policy:

curl -sS "https://api.aptiva.cloud/external/api/agents/42/recall-policy" \
  -H "X-API-Key: $APTIVA_API_KEY"
{ "recallPolicy": "all" }

Un agente per cui la policy non è mai stata impostata risponde all. Un agente che non è in uscita, cioè in entrata o senza numero, risponde null: la policy non si applica.

Si cambia con PUT /agents/ID/recall-policy e il valore nuovo nel body. La risposta è lo stato salvato, e ripetere la stessa richiesta non cambia nulla:

curl -sS -X PUT "https://api.aptiva.cloud/external/api/agents/42/recall-policy" \
  -H "X-API-Key: $APTIVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "recallPolicy": "ai_recall_only" }'
{ "recallPolicy": "ai_recall_only" }
StatocodeQuandoCosa fare
404not_foundL'agente non esiste o appartiene a un'altra aziendaVerifica l'id
409RECALL_POLICY_OUTBOUND_AGENT_REQUIREDScrittura su un agente che non è in uscita: nulla viene salvatoUsa l'id di un agente in uscita
409AGENT_CONFIG_INVALIDConfigurazione salvata non coerente, non leggibile né aggiornabile in sicurezzaScrivici con l'X-Request-ID
422validation_errorValore sconosciuto, null o campo mancante nella scritturaUsa all, ai_recall_only o none

Due comportamenti da conoscere. Un cambio di policy non tocca le chiamate già in coda: un tentativo o un richiamo programmato prima del cambio parte comunque, e si annulla dal pannello, nella pagina «Richiami pianificati». Una chiamata in corso al momento del cambio segue invece la policy nuova, perché viene letta a fine chiamata.

La stessa scelta si fa dal pannello, nelle impostazioni dell'agente, alla sezione «Richiami automatici»: lì i tre valori si chiamano «Tutti i richiami», «Solo i richiami decisi dall'AI» e «Nessun richiamo».

Leggere le chiamate: esito, trascrizione, durate#

L'elenco delle chiamate è GET /calls, paginato come i lead; per vedere le più recenti per prime usa isAscending=false, e con orderBy puoi ordinarle anche per duration o outcome. Ogni elemento è in forma compatta:

[
  {
    "id": 9876,
    "lead": {
      "id": 1285,
      "email": "mario.rossi@example.com",
      "name": "Mario",
      "lastName": "Rossi",
      "phone": "+393331234567",
      "leadCompanyName": "Rossi Srl"
    },
    "createdAt": "2026-09-11T09:32:41.115Z",
    "outcome": 200,
    "duration": 184,
    "direction": 1,
    "callInfo": null,
    "voicebotConfig": { "id": 42, "name": "Richiamo preventivi", "direction": 1 }
  }
]

Il dettaglio GET /calls/ID aggiunge il lead completo (con additionalInfo), la trascrizione, l'analisi e le durate:

{
  "id": 9876,
  "lead": { "id": 1285, "phone": "+393331234567", "name": "Mario", "lastName": "Rossi", "email": "mario.rossi@example.com", "leadCompanyName": "Rossi Srl", "createdAt": "2026-09-11T09:32:10.482Z", "additionalInfo": { "origine": "form preventivo" }, "leadMetadata": { "external_lead_id": "crm-88213" } },
  "createdAt": "2026-09-11T09:32:41.115Z",
  "outcome": 200,
  "duration": 184,
  "direction": 1,
  "voicebotConfig": { "id": 42, "name": "Richiamo preventivi", "direction": 1 },
  "transcriptionItems": [
    { "timestamp": "2026-09-11T09:32:45.002Z", "role": "assistant", "content": "Buongiorno, sono l'assistente di Rossi Energia, la chiamo per il preventivo richiesto sul nostro sito...", "aiInterrupted": false },
    { "timestamp": "2026-09-11T09:32:52.410Z", "role": "user", "content": "Sì, buongiorno, volevo capire i tempi di installazione.", "aiInterrupted": false }
  ],
  "callAnalysis": {
    "recap": "Il cliente conferma l'interesse per l'impianto da 6 kW e chiede i tempi di installazione; concordato che un tecnico lo richiami entro venerdì per fissare il sopralluogo.",
    "humanCallbackRequest": {
      "target": "specific_person",
      "targetLabel": "tecnico commerciale",
      "preferredTimeText": "entro venerdì, pomeriggio",
      "reason": "fissare il sopralluogo"
    }
  },
  "recallAt": null,
  "callInfo": null,
  "durationWithAiSec": null,
  "sipBridgeDurationSec": null
}

I campi da conoscere:

  • outcome: codice numerico dell'esito (tabella sotto). È null finché la chiamata non è conclusa.
  • direction: 0 chiamata in entrata, 1 chiamata in uscita.
  • transcriptionItems: la conversazione turno per turno, con role (assistant è l'agente, user è la persona), content e aiInterrupted (true se la persona ha interrotto l'agente mentre parlava).
  • callAnalysis.recap: il riepilogo della chiamata scritto dall'agente; callAnalysis.humanCallbackRequest: presente se la persona ha chiesto di essere richiamata da un umano, con target (operator o specific_person), targetLabel, preferredTimeText e reason.
  • recallAt: data e ora del prossimo tentativo programmato, dopo una chiamata senza risposta (cadenza dei richiami) o quando l'analisi post-chiamata ha fissato un richiamo; null negli altri casi, e sempre null dopo una chiamata senza risposta se la policy dei richiami dell'agente non è all. Attenzione al formato: è espresso in UTC senza indicazione del fuso, a differenza di createdAt che termina con Z. Un valore come 2026-09-12T11:00:00 va letto come le 11:00 UTC, cioè le 13:00 ora italiana con l'ora estiva: prima di interpretarlo, trattalo come UTC (per esempio aggiungendo Z).
  • durationWithAiSec e sipBridgeDurationSec: valorizzati solo se c'è stato un trasferimento di chiamata a una persona che ha risposto; il primo sono i secondi con l'agente AI prima del passaggio, il secondo i secondi della conversazione dopo. Senza trasferimento sono entrambi null. duration è la durata complessiva in secondi.
  • callInfo: informazioni tecniche aggiuntive, non strutturate; può essere null.

Non c'è la registrazione audio nelle API: si ascolta dal pannello.

I codici di outcome#

outcomeSignificatoFamiglia
101OccupatoIl destinatario non è stato raggiunto
102Nessuna rispostaIl destinatario non è stato raggiunto
103Segreteria telefonicaIl destinatario non è stato raggiunto
200Chiamata riuscita: l'obiettivo è stato raggiuntoConversazione avvenuta
201Chiamata completata: conversazione avvenuta senza un esito esplicitoConversazione avvenuta
300Chiamata interrotta (esito storico: non viene più assegnato)Conversazione interrotta
301La persona ha chiesto di essere richiamata (esito storico: non viene più assegnato)Conversazione interrotta
302L'agente ha chiuso la chiamata per un altro motivoConversazione interrotta
500Chiamata fallita per errore tecnico (non conta nella cadenza dei richiami)Errore
550Credito insufficiente: la chiamata non è stata effettuata (non conta nella cadenza dei richiami)Errore

Per un cruscotto o un CRM, la lettura più utile è per famiglia: 1xx riprova (o lascia fare alla cadenza dei richiami), 2xx e 3xx lavora sul recap e guarda humanCallbackRequest e recallAt, 5xx controlla il credito o scrivici.

Esempio completo: dal form del sito all'esito della chiamata#

Lo scenario: un visitatore compila il form «richiedi un preventivo» sul tuo sito e vuoi che l'agente lo chiami entro pochi secondi, poi vuoi vedere nel tuo CRM com'è andata. Tutto avviene sul tuo server, mai nel browser (la chiave API non deve uscire dal server).

Passo 1: il form invia i dati al tuo server. Il tuo backend riceve nome, telefono, email e la richiesta.

Passo 2: il server crea il lead. Salvi la richiesta in additionalInfo, così l'agente sa di cosa parlare, e il tuo id in leadMetadata.externalLeadId.

curl -sS -X POST "https://api.aptiva.cloud/external/api/leads/create" \
  -H "X-API-Key: $APTIVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "<IL_TUO_NUMERO_DI_PROVA>",
    "name": "Mario",
    "lastName": "Rossi",
    "email": "mario.rossi@example.com",
    "additionalInfo": {
      "origine": "form preventivo sito",
      "richiesta": "impianto fotovoltaico 6 kW con accumulo, casa indipendente a Bergamo",
      "inviato_il": "2026-09-11T09:32:00+02:00"
    },
    "leadMetadata": { "externalLeadId": "crm-88213" }
  }'

Risposta: 201 con {"id": 1285}. Salva 1285 accanto al contatto nel tuo CRM.

Passo 3: il server avvia la chiamata, forzandola. È il contatto stesso ad aver chiesto di essere contattato ora, quindi force a true: parte subito anche se in passato lo avevi già chiamato.

curl -sS -X POST "https://api.aptiva.cloud/external/api/calls/start" \
  -H "X-API-Key: $APTIVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "leadId": 1285, "agentId": 42, "force": true }'

Risposta: 204. Se ricevi 422 con OUTBOUND_CONFIG_REQUIRED, l'agente 42 non è un agente per chiamate in uscita: nel pannello controlla la direzione dell'agente.

Sull'orario: la chiamata parte subito a qualunque ora, anche se il form arriva di sera. Per un lead che ha appena chiesto di essere contattato è il comportamento utile: chi ha appena lasciato i suoi dati è nel momento di massimo interesse, e richiamarlo in pochi secondi fa la differenza. Se per la tua attività preferisci non chiamare in certi orari, avvia la chiamata dal tuo sistema all'orario che scegli: force a false non la rimanda, perché la prima chiamata a un lead nuovo parte comunque subito.

Passo 4: il server legge l'esito. Qualche minuto dopo (o con un controllo periodico ogni minuto), leggi le chiamate più recenti e prendi quelle del tuo lead:

curl -sS "https://api.aptiva.cloud/external/api/calls?pageNumber=0&pageSize=50&isAscending=false" \
  -H "X-API-Key: $APTIVA_API_KEY"

Nell'array cerca gli elementi con lead.id uguale a 1285; il primo è la chiamata più recente. Se non trovi ancora nulla, la chiamata è in coda o programmata: il record viene creato nel momento in cui viene eseguita. Finché outcome è null la chiamata è in corso. Quando ha un valore, chiedi il dettaglio:

curl -sS "https://api.aptiva.cloud/external/api/calls/9876" \
  -H "X-API-Key: $APTIVA_API_KEY"

Passo 5: aggiorna il tuo CRM. Con outcome 200 o 201 salva callAnalysis.recap come nota e, se humanCallbackRequest è presente, crea un'attività per la persona indicata con preferredTimeText. Con 101, 102 o 103 non fare niente, se l'agente ha la policy dei richiami predefinita: la cadenza ha già programmato il tentativo successivo, e recallAt ti dice quando (in UTC); segna nel CRM «richiamo automatico previsto» e ricontrolla dopo quell'ora. Con le policy ai_recall_only e none, invece, il prossimo tentativo lo decide il tuo sistema. Dopo ogni chiamata con risposta (200, 201, 302) guarda anche recallAt: se è valorizzato, l'analisi ha già programmato un richiamo e non devi ripianificarlo tu.

In tutto sono due richieste per far partire la chiamata e una o due per leggere l'esito. Per confrontare i tempi di risposta reali con quelli del tuo team, createdAt della chiamata meno inviato_il del form ti dà i secondi trascorsi fra la richiesta e la chiamata.

Prossimi passi#

  1. Prova il flusso con il tuo numero: crea il lead, avvia la chiamata con force a true e leggi il dettaglio con la trascrizione. Se non hai ancora la chiave, la guida ai primi passi spiega dove trovarla.
  2. Se l'agente deve poter passare la chiamata a una persona, configura il trasferimento con API: configurare il trasferimento di chiamata, e leggi come funziona in Trasferimento assistito: l'agente AI passa la chiamata.
  3. Per volumi importanti le API hanno due richieste in blocco: con una sola richiesta POST /leads/batch-upsert crea o aggiorna fino a 500 lead, e POST /calls/batch-start avvia fino a 100 chiamate. Ogni elemento segue le regole della richiesta singola, cadenza dei richiami compresa, e ha nella risposta il proprio esito. Per volumi più grandi, o per esigenze che le API non coprono ancora, scrivici dalla pagina Contatti: troviamo insieme la soluzione. I costi delle chiamate sono nella pagina Prezzi.

Fonti

  1. 1.Aptiva — reference interattiva delle API (OpenAPI)
  2. 2.RFC 7396 — JSON Merge Patch (la semantica di fusione di additionalInfo)
  3. 3.ITU-T E.164 — piano di numerazione internazionale (formato dei numeri)
  4. 4.libphonenumber — libreria di validazione e normalizzazione dei numeri di telefono
  5. 5.RFC 9110 — HTTP Semantics
Ap

A cura di Redazione Aptiva

Costruiamo e gestiamo in casa la piattaforma di agenti vocali AI di Aptiva: queste guide nascono dall'esperienza diretta sul prodotto e sulle chiamate reali dei clienti.

Domande frequenti

Le risposte in breve

Non viene creato un duplicato: i campi che invii aggiornano il lead esistente (quelli che non invii restano com'erano) e la risposta è 201 con l'id del lead esistente. La chiave di unicità è il numero di telefono normalizzato all'interno della tua azienda.

Vuoi vederlo sul tuo flusso di chiamate?

Attiva il tuo agente vocale AI e provalo con meno di 10€, senza vincoli.