Form — Server-to-server
Crea e gestisci pratiche direttamente dal tuo backend, autenticandoti con la api_token (Bearer). Base URL: https://app.dokicasa.it.
Endpoint principali
| Method | Path | Cosa fa |
|---|---|---|
GET | /api/v3/catalog-forms | Tutti i form disponibili (servizi, contratti, bundle) con slug e form_url — pubblico |
GET | /api/list-form/canone-concordato-{citta} | Lista degli step del flusso per una città |
GET | /api/v3/form/{slug} | Schema (campi) di un form |
POST | /api/v3/form/{slug} | Submit del form → crea una pratica |
PUT | /api/v3/form/{bundleUserServiceId} | Aggiorna un form già inviato |
GET | /api/v3/user/{id}/services | Lista pratiche (filtrabile per external_id) |
GET | /api/v3/practices/{practice} | Dettaglio + stato di una pratica |
GET | /api/v3/practices/{practice}/tasks | Task collegati alla pratica |
POST | /api/v3/tasks/{task}/comments | Rispondi a un task (commento + allegati) |
GET | /api/v3/contract/{id}/pdf | Scarica il PDF di una pratica-contratto (anche step di bundle) |
GET | /api/v3/contract/{id}/doc | Scarica il Word (.docx) di una pratica-contratto (richiede il download Word abilitato sull'account) |
POST | /api/v3/practices/{practice}/duplicate-partner | Duplica una pratica (cambio inquilino): clona gli step duplicabili in una nuova pratica |
0. Scopri i form disponibili
Un'unica chiamata pubblica ti dà tutti i form, ordinati per nome:
curl https://app.dokicasa.it/api/v3/catalog-forms -H "Accept: application/json"Ogni voce ha kind (service / contract / bundle), name, slug e form_url. I bundle includono steps (un form per step):
[
{ "kind": "service", "name": "Visura catastale", "slug": "visura-catastale",
"form_url": "/api/v3/form/visura-catastale", "steps": [] },
{ "kind": "bundle", "name": "Canone Concordato", "slug": "canone-concordato",
"form_url": null, "steps": [
{ "step": 1, "name": "Contratto", "type": "Contract",
"slug": "contratto-locazione", "form_url": "/api/v3/form/contratto-locazione" }
] }
]La lista interattiva (con copia-slug) è anche in SDK JavaScript → Form disponibili.
1. Crea una pratica
$res = Http::withToken(env('DOKICASA_TOKEN'))
->acceptJson()
->post('https://app.dokicasa.it/api/v3/form/locazione-ad-uso-abitativo-4-4', [
'form' => [ /* ...risposte dei campi... */ ],
'metadata' => [
'external_id' => 'ordine_8842', // il TUO id, per ritrovarla dopo
],
])
->json();
// $res['id'], $res['external_id']const res = await fetch(
'https://app.dokicasa.it/api/v3/form/locazione-ad-uso-abitativo-4-4',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.DOKICASA_TOKEN}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({
form: { /* ...risposte... */ },
metadata: { external_id: 'ordine_8842' },
}),
}
).then((r) => r.json())curl -X POST \
https://app.dokicasa.it/api/v3/form/locazione-ad-uso-abitativo-4-4 \
-H "Authorization: Bearer $DOKICASA_TOKEN" \
-H "Content-Type: application/json" \
-d '{"form":{},"metadata":{"external_id":"ordine_8842"}}'WARNING
POST /api/v3/form/{slug} può scalare credito dal wallet del partner. Verifica il saldo prima di automatizzare creazioni massive.
Come si compila form
form è un oggetto indicizzato per slug del campo; ogni valore è { "type": "<tipo>", "value": <valore> }. Recupera i campi (con type, is_required, depends_from, selectable_options) da GET /api/v3/form/{slug}.
{
"form": {
// selectable: basta la label come stringa
"tipologia_ape_5": { "type": "selectable", "value": "Locazione" },
"numero_di_telefono_per_contatto_5": { "type": "varchar", "value": "3331234567" },
"dati_immobile_6_6": { "type": "address", "value": "Via Cavour 1, 50100 Firenze (FI)" }
},
"metadata": { "external_id": "ordine_8842" }
}Attenzione ad alcuni tipi:
selectable— invia lalabeldell'opzione come stringa (viene normalizzata in automatico):{ "type": "selectable", "value": "Locazione" }. Se il campo è a scelta multipla (is_multiple: truenello schema), invia un array di label:{ "type": "selectable", "value": ["Opzione A", "Opzione B"] }. In ogni casovaluedeve contenere stringhe (la label): non inviare un oggetto annidato né un array dentrovalue(es.{ "value": ["..."] }come oggetto singolo non è valido).customer_data— array di oggetti, uno per persona/entità:[{ "type": "Persona Fisica", "name": "...", "address": "...", "fiscal_code": "...", ... }]. I campi richiesti dipendono daltype(vedifieldsnello schema). Questi blocchi vanno sempre compilati anche quando lo schema riportais_required: 0.immobile(es.blocco_immobile) — lista di oggetti immobile, ognuno conindirizzo,categoria_catastale,foglio_immobile,parcella_immobile,subalterno_immobile,rendita_immobile. Un singolo oggetto (non in lista) non è valido.file—valueè una lista di oggetti, uno per allegato, ognuno confilenameecontent. Non si usamultipart/form-datae non esiste un endpoint di upload separato: il contenuto viaggia dentro il campo (vedi Allegare un file). Molti campifilesono richiesti solo su determinati rami di unselectable(depends_from): scegliendo l'altro ramo non servono.- Un campo è obbligatorio solo se attivo: se la sua
depends_fromnon è soddisfatta viene ignorato.
Campi che si possono fornire più tardi
Alcuni campi obbligatori si possono rimandare: nello schema hanno has_skip_button: true e skip_button_text con il testo che vede l'utente (per esempio l'APE dell'attestazione: "Ne sono in possesso, lo fornirò successivamente").
Per rimandarli basta non mandarli: ometti la chiave dal form, oppure inviala con valore vuoto (null, "", []). Non serve nessun campo aggiuntivo e non c'è un valore speciale da usare.
{
"form": {
// ape_1_1_7 semplicemente non c'è: verrà richiesto dopo
"tipologia_ape_5": { "type": "selectable", "value": "Locazione" }
}
}La pratica viene accettata e il campo risulta rimandato, esattamente come se l'utente avesse premuto il pulsante dall'interfaccia: il nostro backoffice vede che il documento non era disponibile e continua a richiederlo. Il documento va poi fornito con i canali abituali, non c'è un endpoint dedicato.
WARNING
Vale solo per i campi con has_skip_button: true. Tutti gli altri campi obbligatori attivi, se mancanti, restituiscono 422 con "Campo obbligatorio mancante.".
Allegare un file (campi di tipo file)
I campi di tipo file si inviano nello stesso JSON del form, non come multipart/form-data. value è una lista di oggetti, uno per allegato:
{
"form": {
"documenti_roma_1_1_7": {
"type": "file",
"value": [
{
"filename": "documenti-firmati.pdf",
"content": "JVBERi0xLjQKJeLjz9MK..." // base64 del file
}
]
}
},
"metadata": { "external_id": "ordine_8842" }
}content accetta due formati, riconosciuti in automatico:
| Formato | Esempio | Comportamento |
|---|---|---|
| base64 | "JVBERi0xLjQK..." | il contenuto viene decodificato e salvato |
| URL pubblica | "https://tuo-dominio.it/doc.pdf" | il file viene scaricato dal nostro server |
filename è facoltativo ma consigliato: da lì ricaviamo l'estensione. Se manca, proviamo a dedurla dall'URL. content invece è obbligatorio: senza, la chiamata risponde 400 con File "<nome>" has no content.
Esempio completo con curl (base64 di un PDF locale):
CONTENT=$(base64 -w0 documenti-firmati.pdf) # su macOS: base64 -i documenti-firmati.pdf
curl -X POST "https://app.dokicasa.it/api/v3/form/attestazione-contratto-locazione-roma" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"form\": {
\"documenti_roma_1_1_7\": {
\"type\": \"file\",
\"value\": [{ \"filename\": \"documenti-firmati.pdf\", \"content\": \"$CONTENT\" }]
}
},
\"metadata\": { \"external_id\": \"ordine_8842\" }
}"WARNING
value deve essere una lista di oggetti. Passare il solo nome del file come stringa ("value": "contratto.pdf") fa rispondere 201 ma non allega nulla: la pratica viene creata con attachments: []. Allo stesso modo non funzionano multipart/form-data, né la parte attachments[] — che serve solo per gli allegati delle risposte ai task (vedi Rispondi a un task).
NOTE
Se il campo è obbligatorio e attivo, inviarlo vuoto ("", [], [""]) fa rispondere 422 Campo obbligatorio mancante. Un campo file obbligatorio resta tale anche quando nel form web mostra il pulsante "salta".
Pratica singola vs bundle
L'external_id è il tuo id (della tua piattaforma) e determina anche il raggruppamento:
- Primo invio con un
external_idnuovo → crea la pratica. Se lo slug è un servizio/contratto singolo nasce una pratica autonoma; se è lo step 1 di un bundle viene creato il bundle raccoglitore. - Invio successivo con lo stesso
external_id→ lo step viene agganciato al bundle già avviato (step 2, 3, …).
L'external_id resta legato alla pratica e la rende ritrovabile con filter[external_id].
I bundle vanno sempre avviati dallo step 1
Se il primo invio con un external_id nuovo usa lo slug di uno step interno (step 2, 3, …), il raccoglitore non può essere creato e la pratica nascerebbe isolata, senza gli step precedenti. In questo caso la risposta è 422:
{
"error": "BUNDLE_NOT_STARTED",
"message": "The service \"Creazione Documenti Canone Concordato Roma\" is step 4 of the bundle \"Canone Concordato Roma\" and cannot be the first call for a new external_id. Start the bundle by calling the first step (\"foglio-caratteristiche-immobile-roma\") with the same external_id, then call the remaining steps.",
"first_step_slug": "foglio-caratteristiche-immobile-roma"
}Il campo first_step_slug contiene lo slug da cui partire. Gli step del bundle si scoprono con GET /api/v3/catalog-forms (vedi Scopri i form disponibili).
Creare uno step come pratica singola: metadata.standalone
Alcuni servizi fanno parte di un bundle ma hanno senso anche da soli. Se vuoi crearne uno senza bundle, dichiaralo esplicitamente:
{
"form": { },
"metadata": {
"external_id": "ordine_8842",
"standalone": true
}
}Con standalone: true la pratica nasce autonoma: non viene cercato né creato nessun raccoglitore, e il controllo sopra non si applica. Ometti il parametro quando vuoi il comportamento normale a bundle.
Un external_id non si riusa fra pratica singola e bundle
Se un external_id è già stato usato per una pratica singola, non può poi fare da raccoglitore per gli step di un bundle. In quel caso la risposta è 409:
{
"error": "EXTERNAL_ID_NOT_A_BUNDLE",
"message": "The external_id \"ordine_8842\" is already used by a standalone practice and cannot be used to add bundle steps. Use a different external_id for the bundle."
}Usa un external_id diverso per il bundle.
2. Ritrova le tue pratiche
$practices = Http::withToken(env('DOKICASA_TOKEN'))
->acceptJson()
->get('https://app.dokicasa.it/api/v3/user/me/services', [
'filter[external_id]' => 'ordine_8842',
])
->json();3. Stato e task di una pratica
$id = 84213; // id pratica oppure uuid pubblico
$practice = Http::withToken(env('DOKICASA_TOKEN'))->acceptJson()
->get("https://app.dokicasa.it/api/v3/practices/{$id}")->json();
// $practice['status'] → es. WAITING / DOING / ...
$tasks = Http::withToken(env('DOKICASA_TOKEN'))->acceptJson()
->get("https://app.dokicasa.it/api/v3/practices/{$id}/tasks")->json();
// $tasks['tasks'] → Activity (type=TASK) con status TO_DO / DOING / DONETIP
Con l'uuid al posto dell'id numerico, GET /practices/{practice} e .../tasks sono pubbliche (senza Bearer): comode per un link in sola lettura. Con l'id numerico servono Bearer e proprietà della pratica.
4. Rispondi a un task
Quando una pratica è in lavorazione il backoffice può assegnarti dei task (Activity con type = TASK, leggibili con GET /practices/{practice}/tasks): ad esempio caricare un documento o fornire un'informazione mancante.
Per rispondere invii un commento (testo e/o allegati) al task. La chiamata:
- richiede sempre il Bearer
api_tokendell'utente; - accetta solo task assegnati a te (
assigned_to) e agganciati a una pratica di tua proprietà; altrimenti403; - riporta il task in stato
DOINGe lo riassegna al backoffice, che riceve la notifica.
WARNING
{task} è l'id del singolo task, cioè il campo id di un elemento dell'array tasks[] restituito da GET /practices/{practice}/tasks — non l'id della pratica (practice_id / id del raccoglitore). Passando l'id della pratica ottieni 404 (o 403 se quell'id coincide per caso con un task non tuo).
// GET /api/v3/practices/84213/tasks
{
"practice_id": 84213, // ❌ NON questo
"tasks": [
{ "id": 99812, ... } // ✅ questo: {task} = 99812
]
}$taskId = 99812;
// Solo testo
$comment = Http::withToken(env('DOKICASA_TOKEN'))->acceptJson()
->post("https://app.dokicasa.it/api/v3/tasks/{$taskId}/comments", [
'comment' => 'Ho caricato il documento richiesto.',
])
->json();
// Con allegati (multipart)
$comment = Http::withToken(env('DOKICASA_TOKEN'))->acceptJson()
->attach('attachments[]', file_get_contents('/path/ci.pdf'), 'ci.pdf')
->post("https://app.dokicasa.it/api/v3/tasks/{$taskId}/comments", [
'comment' => 'In allegato la carta d\'identità.',
])
->json();const taskId = 99812
const form = new FormData()
form.append('comment', "In allegato la carta d'identità.")
form.append('attachments[]', fileBlob, 'ci.pdf') // opzionale
const comment = await fetch(
`https://app.dokicasa.it/api/v3/tasks/${taskId}/comments`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.DOKICASA_TOKEN}`,
Accept: 'application/json',
},
body: form, // niente Content-Type manuale: lo imposta FormData
}
).then((r) => r.json())curl -X POST \
https://app.dokicasa.it/api/v3/tasks/99812/comments \
-H "Authorization: Bearer $DOKICASA_TOKEN" \
-H "Accept: application/json" \
-F "comment=In allegato la carta d'identità." \
-F "attachments[]=@/path/ci.pdf"NOTE
Devi fornire almeno uno tra comment e attachments[]. La risposta 201 contiene il commento creato; il task passa a DOING in attesa della verifica del backoffice.
5. Scarica il PDF di un contratto
Le pratiche di tipo contratto (type: CONTRACT) espongono il PDF generato, scaricabile on-demand con il Bearer api_token:
GET /api/v3/contract/{id}/pdf{id}è l'id della pratica-contratto: lo stessoidche ti restituiscePOST /api/v3/form/{slug}quando lo slug è un contratto, oppure — se il contratto è uno step di un bundle — l'iddi quello step (user_service_id). Vale quindi sia per i contratti singoli sia per gli step-contratto di un bundle.- Richiede il Bearer e la proprietà della pratica (quelle create via API appartengono al partner). Un
idche non è tuo →401. - Di default risponde con il PDF inline (
Content-Type: application/pdf); con?download_mode=1forza il download come allegato (Content-Disposition: attachment; filename="<nome-contratto>.pdf").
$id = 84213; // id pratica-contratto (dal POST form, o user_service_id dello step)
$pdf = Http::withToken(env('DOKICASA_TOKEN'))
->get("https://app.dokicasa.it/api/v3/contract/{$id}/pdf", [
'download_mode' => 1, // forza attachment; ometti per il PDF inline
])
->body();
file_put_contents("contratto_{$id}.pdf", $pdf);curl -L -X GET \
"https://app.dokicasa.it/api/v3/contract/84213/pdf?download_mode=1" \
-H "Authorization: Bearer $DOKICASA_TOKEN" \
-o contratto_84213.pdfTIP
Il PDF del contratto ti arriva anche come allegato del webhook CLOSED (campo attachments[].content_base64, vedi sotto). Questa GET serve quando lo vuoi on-demand — ad esempio scaricarlo o rigenerarlo prima della chiusura.
NOTE
Parametri opzionali: download_mode=1 (attachment), send_mail=1 (invia il PDF via email al proprietario della pratica), preview (anteprima).
Versione Word (.docx)
Lo stesso contratto è scaricabile anche nella versione editabile Word, con lo stesso Bearer api_token:
GET /api/v3/contract/{id}/doc{id}è lo stesso id usato per il PDF: l'id della pratica-contratto (dalPOST /api/v3/form/{slug}) oppure l'user_service_iddello step-contratto di un bundle.- Richiede il Bearer e la proprietà della pratica.
- Gating aggiuntivo: il download Word dev'essere abilitato sul tuo account partner. Se non lo è, risponde
403con{ "error": "..." }— scrivi al supporto per farlo attivare. - In caso di successo ritorna il file
.docx; gli errori (permesso mancante, generazione non riuscita) sono restituiti in JSON con lo status HTTP appropriato (403,502).
$id = 84213; // stesso id della pratica-contratto usato per il PDF
$docx = Http::withToken(env('DOKICASA_TOKEN'))
->get("https://app.dokicasa.it/api/v3/contract/{$id}/doc")
->body();
file_put_contents("contratto_{$id}.docx", $docx);curl -L -X GET \
"https://app.dokicasa.it/api/v3/contract/84213/doc" \
-H "Authorization: Bearer $DOKICASA_TOKEN" \
-o contratto_84213.docx6. Webhook (eventi verso il tuo backend)
Configura una webhook_url sul tuo account partner: Dokicasa vi POSTa un evento JSON ad ogni passaggio rilevante del ciclo di vita della pratica e dei task. Lo stesso webhook serve sia le pratiche create via API sia quelle create via SDK.
Configurare la webhook_url (in autonomia)
Puoi leggere e aggiornare la webhook_url del tuo account direttamente via API, senza passare dal supporto.
| Method | Path | Descrizione |
|---|---|---|
GET | /api/v3/user/me | Dettagli dell'account loggato (include la webhook_url attuale) |
PATCH | /api/v3/user/me/webhook | Imposta / aggiorna la webhook_url |
curl https://app.dokicasa.it/api/v3/user/me \
-H "Authorization: Bearer $DOKICASA_TOKEN" \
-H "Accept: application/json"curl -X PATCH \
https://app.dokicasa.it/api/v3/user/me/webhook \
-H "Authorization: Bearer $DOKICASA_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"webhook_url":"https://tuo-server.com/webhooks/dokicasa"}'Il body accetta webhook_url (URL valido, obbligatorio come chiave). La risposta è { "webhook_url": "..." }. Per disattivare i webhook passa null o stringa vuota:
curl -X PATCH https://app.dokicasa.it/api/v3/user/me/webhook \
-H "Authorization: Bearer $DOKICASA_TOKEN" \
-H "Content-Type: application/json" \
-d '{"webhook_url":null}'IMPORTANT
La webhook_url è per-ambiente: staging (testing.dokicasa.it) e produzione (app.dokicasa.it) hanno database separati, quindi il valore che imposti vale solo per l'ambiente su cui chiami l'API. Configura il receiver di staging chiamando la PATCH su testing.dokicasa.it e quello di produzione su app.dokicasa.it: così gli eventi di test raggiungono il tuo endpoint di staging e solo quelli reali arrivano in produzione.
Eventi
event_type | Quando |
|---|---|
CREATED | Pratica creata |
UPDATED | Pratica aggiornata |
CLOSED | Pratica conclusa (chiusa dal backoffice) |
NEW_TASK | Il backoffice ti apre un task sulla pratica |
UPDATE_TASK | Un task viene aggiornato o chiuso (task.status = "DONE") |
SERVICE_NOT_AVAILABLE | Servizio non disponibile per quella pratica |
NOTE
Non esiste un evento dedicato alla chiusura del task: chiudere un task è uno UPDATE_TASK con task.status = "DONE".
Payload
Tutti gli eventi condividono lo stesso involucro. Esempio per CLOSED:
{
"event_type": "CLOSED",
"status": "CLOSED",
"external_id": "ordine_8842", // il TUO id (null se non impostato)
"name": "Consulenza ed assistenza legale",
"service_name": "Consulenza ed assistenza legale",
"user_service_id": 99076,
"type": "SERVICE", // SERVICE | CONTRACT
"attachments": [
{ "name": "documento.pdf", "mime_type": "application/pdf",
"url": null, "content_base64": "JVBERi0xLjQK..." }
],
"notes": null,
"details": [],
"metadata": {},
// presenti solo se la pratica è uno step di un bundle:
"bundle_id": 146025,
"bundle_name": "Canone Concordato Roma",
"step": "2"
}Gli allegati possono arrivare inline (content_base64) — es. il PDF del contratto — oppure come url (file pubblico): controlla quale dei due è valorizzato.
Per gli eventi sui task (NEW_TASK / UPDATE_TASK) l'involucro include i campi task e notify:
{
"event_type": "UPDATE_TASK",
"status": "UPDATE_TASK",
"external_id": "ordine_8842",
"user_service_id": 99078,
"type": "SERVICE",
"task": {
"id": 41852,
"type": "TASK",
"status": "DONE", // DONE = task chiuso
"description": "Carica la carta d'identità",
"assigned_to": 1
},
"notify": null // valorizzato su NEW_TASK, null sugli update
}Consegna e idempotenza
- Consegna asincrona (con retry), metodo
POST, body JSON. - Il webhook in uscita non è firmato: proteggi l'endpoint ricevente con una whitelist degli IP di Dokicasa e/o una URL con path segreto.
- Il payload non contiene un id di consegna dedicato: deduplica sulla coppia
user_service_id+event_type(etask.idper gli eventi sui task) e rispondi sempre2xx, anche ai duplicati.
7. Ambiente di staging (sandbox)
Per integrare e testare senza toccare la produzione è disponibile un ambiente di staging con un database isolato:
- Base URL:
https://testing.dokicasa.it - Stesse API, stesso Bearer
api_token(rilasciato sullo staging).
Per provare i webhook in autonomia lo staging espone endpoint che simulano le azioni che normalmente fa il backoffice. Sono disponibili solo in staging (in produzione rispondono 403), richiedono il Bearer e che la pratica/task sia di tua proprietà.
| Method | Path | Webhook generato |
|---|---|---|
POST | /api/v3/sandbox/practices/{userService}/simulate-close | CLOSED (con un PDF di esempio in allegato) |
POST | /api/v3/sandbox/practices/{userService}/simulate-task | NEW_TASK (apre un task assegnato all'utente della pratica) |
POST | /api/v3/sandbox/tasks/{task}/simulate-update | UPDATE_TASK (aggiorna stato/assegnatario/descrizione) |
POST | /api/v3/sandbox/tasks/{task}/simulate-close | UPDATE_TASK con task.status = "DONE" |
{userService} è l'id della pratica (user service); {task} è l'id del singolo task (tasks[].id da GET /practices/{practice}/tasks).
curl -X POST \
https://testing.dokicasa.it/api/v3/sandbox/practices/99076/simulate-close \
-H "Authorization: Bearer $DOKICASA_TOKEN" \
-H "Accept: application/json"curl -X POST \
https://testing.dokicasa.it/api/v3/sandbox/practices/99078/simulate-task \
-H "Authorization: Bearer $DOKICASA_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"description":"Carica la carta d'\''identità"}'curl -X POST \
https://testing.dokicasa.it/api/v3/sandbox/tasks/41852/simulate-close \
-H "Authorization: Bearer $DOKICASA_TOKEN" \
-H "Accept: application/json"Parametri opzionali (JSON body): simulate-task accetta description e status (default TO_DO); simulate-update accetta status, assigned_to, description e user_service_status. La risposta include webhook_sent (false se non hai una webhook_url configurata).
8. Duplica una pratica (cambio inquilino)
Quando su un immobile con una pratica già conclusa cambia l'inquilino, invece di ri-eseguire il Calcolo a mano puoi duplicare la pratica: tutti gli step duplicabili (immobile e calcolo) vengono clonati in una nuova pratica con un nuovo external_id, pronta per registrare il nuovo inquilino.
POST /api/v3/practices/{practice}/duplicate-partner
Authorization: Bearer <token>{practice} è l'id pratica (bundle) che ottieni da GET /api/v3/user/{id}/services (campo practice_id).
Vengono clonati sempre tutti gli step duplicabili della pratica (immobile e calcolo): non si passa l'elenco degli step.
Body (tutti opzionali):
| Campo | Tipo | Default | Descrizione |
|---|---|---|---|
external_id | string | — | Nuovo external_id della pratica duplicata (per i PUT successivi) |
bundle_name | string | "<nome> (copia)" | Nome della nuova pratica |
Billing: per ogni step clonato vale la stessa logica del submit (§1): se lo step è gratuito per il tuo account non viene addebitato nulla, altrimenti prima si consuma un credito compatibile e in mancanza si scala il castelletto (wallet). Se su uno step a pagamento manca sia il credito sia la capienza wallet → 401 con il numero di step, e niente viene creato (operazione atomica: o si duplica tutto o niente).
curl -X POST "https://app.dokicasa.it/api/v3/practices/45210/duplicate-partner" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"external_id":"pratica-2026-00987"}'Risposta 201:
{
"practice_id": 45999,
"external_id": "pratica-2026-00987",
"steps": [
{ "step": 1, "bundle_user_service_id": 88123, "name": "Creazione Documenti Canone Concordato del 15/07/2026" },
{ "step": 2, "bundle_user_service_id": 88124, "name": "Certificazione Contratto Locazione del 15/07/2026" }
]
}Con i bundle_user_service_id restituiti aggiorni poi i dati del nuovo inquilino via PUT /api/v3/form/{bundle_user_service_id} (vedi §1).
Errori:
403la pratica non è tua ·422pratica non duplicabile o nessuno step duplicabile ·401castelletto insufficiente (con lostep) ·403piano API non abilitato per quel servizio.
Riferimento OpenAPI
La specifica completa (parametri, schemi, risposte) è qui sotto, generata dalla stessa spec pubblicata anche su api.dokicasa.it/api/documentation.