Skip to content

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

MethodPathCosa fa
GET/api/v3/catalog-formsTutti 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}/servicesLista pratiche (filtrabile per external_id)
GET/api/v3/practices/{practice}Dettaglio + stato di una pratica
GET/api/v3/practices/{practice}/tasksTask collegati alla pratica
POST/api/v3/tasks/{task}/commentsRispondi a un task (commento + allegati)
GET/api/v3/contract/{id}/pdfScarica il PDF di una pratica-contratto (anche step di bundle)
GET/api/v3/contract/{id}/docScarica il Word (.docx) di una pratica-contratto (richiede il download Word abilitato sull'account)
POST/api/v3/practices/{practice}/duplicate-partnerDuplica 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:

bash
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):

json
[
  { "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

php
$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']
js
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())
bash
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}.

jsonc
{
  "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 la label dell'opzione come stringa (viene normalizzata in automatico): { "type": "selectable", "value": "Locazione" }. Se il campo è a scelta multipla (is_multiple: true nello schema), invia un array di label: { "type": "selectable", "value": ["Opzione A", "Opzione B"] }. In ogni caso value deve contenere stringhe (la label): non inviare un oggetto annidato né un array dentro value (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 dal type (vedi fields nello schema). Questi blocchi vanno sempre compilati anche quando lo schema riporta is_required: 0.
  • immobile (es. blocco_immobile) — lista di oggetti immobile, ognuno con indirizzo, categoria_catastale, foglio_immobile, parcella_immobile, subalterno_immobile, rendita_immobile. Un singolo oggetto (non in lista) non è valido.
  • filevalue è una lista di oggetti, uno per allegato, ognuno con filename e content. Non si usa multipart/form-data e non esiste un endpoint di upload separato: il contenuto viaggia dentro il campo (vedi Allegare un file). Molti campi file sono richiesti solo su determinati rami di un selectable (depends_from): scegliendo l'altro ramo non servono.
  • Un campo è obbligatorio solo se attivo: se la sua depends_from non è 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.

jsonc
{
  "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:

jsonc
{
  "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:

FormatoEsempioComportamento
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):

bash
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_id nuovo → 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:

json
{
  "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:

json
{
  "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:

json
{
  "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

php
$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

php
$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 / DONE

TIP

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_token dell'utente;
  • accetta solo task assegnati a te (assigned_to) e agganciati a una pratica di tua proprietà; altrimenti 403;
  • riporta il task in stato DOING e 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}/tasksnon 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).

jsonc
// GET /api/v3/practices/84213/tasks
{
  "practice_id": 84213,   // ❌ NON questo
  "tasks": [
    { "id": 99812, ... }  // ✅ questo: {task} = 99812
  ]
}
php
$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();
js
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())
bash
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 stesso id che ti restituisce POST /api/v3/form/{slug} quando lo slug è un contratto, oppure — se il contratto è uno step di un bundle — l'id di 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 id che non è tuo → 401.
  • Di default risponde con il PDF inline (Content-Type: application/pdf); con ?download_mode=1 forza il download come allegato (Content-Disposition: attachment; filename="<nome-contratto>.pdf").
php
$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);
bash
curl -L -X GET \
  "https://app.dokicasa.it/api/v3/contract/84213/pdf?download_mode=1" \
  -H "Authorization: Bearer $DOKICASA_TOKEN" \
  -o contratto_84213.pdf

TIP

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 (dal POST /api/v3/form/{slug}) oppure l'user_service_id dello 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 403 con { "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).
php
$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);
bash
curl -L -X GET \
  "https://app.dokicasa.it/api/v3/contract/84213/doc" \
  -H "Authorization: Bearer $DOKICASA_TOKEN" \
  -o contratto_84213.docx

6. 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.

MethodPathDescrizione
GET/api/v3/user/meDettagli dell'account loggato (include la webhook_url attuale)
PATCH/api/v3/user/me/webhookImposta / aggiorna la webhook_url
bash
curl https://app.dokicasa.it/api/v3/user/me \
  -H "Authorization: Bearer $DOKICASA_TOKEN" \
  -H "Accept: application/json"
bash
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:

bash
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_typeQuando
CREATEDPratica creata
UPDATEDPratica aggiornata
CLOSEDPratica conclusa (chiusa dal backoffice)
NEW_TASKIl backoffice ti apre un task sulla pratica
UPDATE_TASKUn task viene aggiornato o chiuso (task.status = "DONE")
SERVICE_NOT_AVAILABLEServizio 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:

jsonc
{
  "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:

jsonc
{
  "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 (e task.id per gli eventi sui task) e rispondi sempre 2xx, 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à.

MethodPathWebhook generato
POST/api/v3/sandbox/practices/{userService}/simulate-closeCLOSED (con un PDF di esempio in allegato)
POST/api/v3/sandbox/practices/{userService}/simulate-taskNEW_TASK (apre un task assegnato all'utente della pratica)
POST/api/v3/sandbox/tasks/{task}/simulate-updateUPDATE_TASK (aggiorna stato/assegnatario/descrizione)
POST/api/v3/sandbox/tasks/{task}/simulate-closeUPDATE_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).

bash
curl -X POST \
  https://testing.dokicasa.it/api/v3/sandbox/practices/99076/simulate-close \
  -H "Authorization: Bearer $DOKICASA_TOKEN" \
  -H "Accept: application/json"
bash
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à"}'
bash
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):

CampoTipoDefaultDescrizione
external_idstringNuovo external_id della pratica duplicata (per i PUT successivi)
bundle_namestring"<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).

bash
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:

json
{
  "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: 403 la pratica non è tua · 422 pratica non duplicabile o nessuno step duplicabile · 401 castelletto insufficiente (con lo step) · 403 piano 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.