Market Scanner integration
Dokicasa's Scanner APIs let you obtain deeds (visure), inspections, notes, cadastral maps and address searches. They are asynchronous: this guide shows how to receive results efficiently, avoiding polling when possible.
🎮 Try the API live
Open the interactive Playground: paste your token, fire each call in sandbox and watch socket and polling arrive in real time, test webhooks, and copy ready-to-use code in cURL, Python, Node, JavaScript and PHP.
| Base URL | https://api.dokicasa.it |
| Auth | Authorization: Bearer <api_token> |
| Content-Type | application/json |
Documentation and authentication
Interactive docs for all endpoints are on Swagger, Scanner section: api.dokicasa.it/api/documentation#/Scanner
Every call must be authenticated with a Bearer token:
Authorization: Bearer YOUR_API_TOKENWARNING
The token must never end up in public repos or the frontend: treat it like a password.
Main endpoints
Summary table — the full schema is on Swagger.
| Method | Path | What it does |
|---|---|---|
POST | /api/scanner/{type} | Create a scanner request (visura / inspection / map / list-addresses / …). Returns scanner_request_id. |
GET | /api/scanner-request/{id} | Request status + result. For PDFs you get the link result.pdf_path here (the file is NOT in the JSON: download it from the link); for list-addresses the data is already in the JSON. |
GET | /api/scanner-request/{id}/results | Structured results for "normal" searches (e.g. properties from an address). |
| WS | private-scanner_request.{id} | Private WebSocket channel for realtime updates. |
Request states
ENQUEUED → SCANNING → SUCCESS
╲→ ERRORENQUEUED and SCANNING are transient; SUCCESS and ERROR are final.
Query types
Every query is a POST /api/scanner/{type}. Below are the available type values (the parameter schema of each is on Swagger, Scanner section).
Data lookups — the result is JSON in the result field:
type | What it does | Field in result |
|---|---|---|
search-fiscal-code | Fiscal code from first name, last name and province | anagrafiche |
tax-data | Properties owned by a fiscal/VAT code | immobili |
catasto | Properties from sheet / parcel | immobili |
intestatari-catasto | Owners of a parcel | intestatari |
list-addresses | Normalize an address → list of streets | addresses |
immobili-by-name | Nationwide properties from first/last name (2 steps) | immobili |
Documents — no API call returns the PDF: the result.pdf_path field holds the link to download it (see note below):
type | Document |
|---|---|
visura-catasto / visura-tax-data | Cadastral survey (from cadastral data / from fiscal-VAT code) |
ispezione-catasto / ispezione-tax-data | Mortgage inspection |
nota-catasto / nota-tax-data | Transcription note |
mappa-catasto | Cadastral map |
elaborato-planimetrico | Floor-plan document |
elenco-immobili | Property list |
IMPORTANT
catasto ≠ cadastral survey. catasto is the property lookup by sheet/parcel and returns the immobili list (JSON data). The cadastral survey PDF is instead visura-catasto (from cadastral data) or visura-tax-data (from fiscal/VAT code). Likewise the mortgage inspection is ispezione-catasto / ispezione-tax-data.
TIP
Inspections and notes require the comune in the body: it is used to resolve the competent land registry office (conservatoria).
IMPORTANT
API calls never return the PDF: they only return the link result.pdf_path. The file is downloaded with a separate GET on that link, passing your Bearer token (authenticated endpoint, not a public anonymous link). The link is stable and permanent (it does not expire): you can store it and reuse it as a reference in your own workflow.
⚠️ With curl the response is binary (Content-Type: application/pdf): without -o curl prints nothing to the terminal (it looks like an empty "200"), but the file is there. Save it:
curl -o visura.pdf "<pdf_path>" -H "Authorization: Bearer <TOKEN>"immobili-by-name — two-phase search
immobili-by-name finds a person's properties nationwide starting from first and last name. Since there can be several namesakes, it works in two phases.
Phase 1 — list of namesakes
POST /api/scanner/immobili-by-name with only first and last name:
{ "first_name": "CHRISTIAN", "last_name": "CANNATA" }When it completes (via socket/polling, like every async call) the result holds the list of namesakes found:
{
"needs_selection": true,
"omonimi": [
{
"value": "9800054753#0#CANNATA#CHRISTIAN#CNNCRS91D08E625S#LIVORNO#08/04/1991#LI",
"nome": "CHRISTIAN", "cognome": "CANNATA",
"codice_fiscale": "CNNCRS91D08E625S",
"data_nascita": "08/04/1991", "luogo_nascita": "LIVORNO (LI)", "sesso": "M"
}
// … other namesakes
]
}needs_selection: true→ you must pick one person from the list.- If there is only one namesake the selection is automatic: you get the Phase 2 result directly.
Phase 2 — properties of the chosen person
Call the same endpoint adding selected_value = the value field of the namesake chosen in Phase 1 (copy it verbatim):
{
"first_name": "CHRISTIAN",
"last_name": "CANNATA",
"selected_value": "9800054753#0#CANNATA#CHRISTIAN#CNNCRS91D08E625S#LIVORNO#08/04/1991#LI"
}When it completes the result holds:
{
"omonimo_selezionato": { "value": "…", "nome": "CHRISTIAN", "cognome": "CANNATA" },
"immobili": { "…": "…" },
"has_soppressi": false
}The properties are persisted: the full list (with owners) is retrieved paginated from GET /api/scanner-request/{id}/results.
NOTE
Both phases are regular async calls: the POST returns ENQUEUED, then via socket or polling you reach SUCCESS and read the result. Very common names may exceed Phase 1's result limit: narrow it down with more specific data.
How it works
All scanner calls are asynchronous. The flow is always the same, only how you receive the status update changes.
- Create the request —
POST /api/scanner/{type}→ you get ascanner_request_idand the initial stateENQUEUED. - Wait for completion — pick one of these (in order of preference):
- WebSocket — instant push, no polling.
- Webhook callback — our server calls yours when it finishes.
- Polling (fallback) — only if you can't use the two above.
- Get the result — when the state is
SUCCESS:- PDF (visure, inspections, maps, notes): the file is already in the
GET /api/scanner-request/{id}response. - Structured searches: call
GET /api/scanner-request/{id}/results.
- PDF (visure, inspections, maps, notes): the file is already in the
Realtime updates via WebSocket
⭐ Recommended for frontend apps
Recommended approach for web/mobile: no polling, instant updates, progress bars and push notifications.
Each request publishes events on a dedicated private channel:
private-scanner_request.{id}where {id} is the scanner_request_id returned by the initial POST.
Socket — host and key per environment
The socket is separate per environment: use the right host and key for where you call.
| Environment | API base URL | wsHost (socket) | key | authEndpoint |
|---|---|---|---|---|
| Sandbox / test | https://api-dev.dokicasa.it | api-dev.dokicasa.it | staging | https://api-dev.dokicasa.it/broadcasting/auth |
| Production | https://api.dokicasa.it | socket.dokicasa.it | ra9jcihuz0sx7vtw8yut | https://api.dokicasa.it/broadcasting/auth |
WARNING
Don't mix environments: connecting to the production socket while calling in sandbox (or vice versa) subscribes the channel but you receive no events (it's a different Reverb). The example below uses production values; for sandbox swap key, wsHost and authEndpoint with the Sandbox row.
Full example (JavaScript + Pusher/Reverb)
import Pusher from 'pusher-js'
const apiToken = 'YOUR_API_TOKEN'
const pusher = new Pusher('ra9jcihuz0sx7vtw8yut', {
wsHost: 'socket.dokicasa.it',
wsPort: 443,
wssPort: 443,
forceTLS: true,
cluster: 'mt1',
disableStats: true,
enabledTransports: ['ws', 'wss'],
authEndpoint: 'https://api.dokicasa.it/broadcasting/auth',
auth: { headers: { Authorization: 'Bearer ' + apiToken } },
})
const { scanner_request_id } = await fetch(
'https://api.dokicasa.it/api/scanner/intestatari-catasto',
{
method: 'POST',
headers: {
Authorization: 'Bearer ' + apiToken,
'Content-Type': 'application/json',
},
body: JSON.stringify({
comune: 'Roma', provincia: 'RM',
foglio: '123', particella: '45',
search_catasto_type: 'F',
}),
}
).then((r) => r.json())
const channel = pusher.subscribe(`private-scanner_request.${scanner_request_id}`)
channel.bind('ScannerRequestNotification', ({ message }) => {
const { status, percentage } = message
if (status === 'SCANNING') updateProgressBar(percentage)
if (status === 'SUCCESS') fetchResult(scanner_request_id)
if (status === 'ERROR') handleError(message)
})
async function fetchResult(id) {
const data = await fetch(`https://api.dokicasa.it/api/scanner-request/${id}`, {
headers: { Authorization: 'Bearer ' + apiToken },
}).then((r) => r.json())
console.log('Final result:', data)
}Webhook & signature
⭐ Recommended for backend integrations
Pass a URL when creating the request: when it finishes, Dokicasa POSTs the result to you. Delivery is async with retries — no setup in the panel.
There are two fields you can add to the initial POST body (either or both):
| Field (in POST body) | Payload you receive | When it fires |
|---|---|---|
callback | compact: { id, type, status, percentage, result, created_at, updated_at, finished_at } | when the scan finishes |
webhook | full: { scanner_request: { …whole request object, with paramsandresult… } } | on the completion event |
{
"type": "catasto",
"comune": "Roma", "foglio": "123", "particella": "45", "search_catasto_type": "F",
"callback": "https://your-server.com/dokicasa/scanner-callback",
"webhook": "https://your-server.com/dokicasa/scanner-webhook"
}Municipalities split into cadastral sections
In some municipalities — Rome, Naples, Genoa — sheet and parcel alone are not enough to identify a property: the cadastral section is needed too. Add sezione_comune to the request in those cases.
{
"type": "catasto",
"comune": "Napoli", "provincia": "NA",
"sezione_comune": "SAN FERDINANDO",
"foglio": "123", "particella": "45"
}It is optional and takes part in identifying the property together with foglio, particella, subalterno and comune: two properties sharing a parcel but sitting in different sections stay distinct. Omitting it in a municipality that uses sections may return the wrong property, or none.
The field is named sezione_comune: a sezione key is ignored without error.
The result you receive has a type-dependent shape — see Result format by type.
callback vs webhook — what's the difference
Same mechanism (a signed POST to your URL when processing finishes, with retries): only the payload shape differs.
{
"id": 2615190,
"type": "VISURA-CATASTO",
"status": "SUCCESS",
"percentage": 98,
"result": { "pdf_path": "https://.../sample-visura-catasto", "is_empty": false },
"created_at": "2026-07-02T23:22:59+02:00",
"updated_at": "2026-07-02T23:23:02+02:00",
"finished_at": "2026-07-02T23:23:02+02:00"
}{
"scanner_request": {
"id": 2615190,
"user_id": 5128,
"type": "VISURA-CATASTO",
"params": { "comune": "ROMA", "provincia": "RM", "foglio": "123", "particella": "45", "tipo_visura": "ANALITICA" },
"result": { "pdf_path": "https://.../sample-visura-catasto", "is_empty": false },
"status": "SUCCESS"
}
}callback | webhook | |
|---|---|---|
| Payload | compact, flat fields | full scanner_request object |
Includes input params | No | Yes |
Signature header (HMAC) | Yes | Yes |
| When it fires | when the scan finishes | when the scan finishes |
Which to use
callback→ lightweight payload: you just react to the outcome (id,status,result). The most common choice.webhook→ the whole record, including the original requestparams: handy to reconcile/log without keeping state on your side.
They're independent: pass one, the other, or both (you then receive two separate POSTs). Both signed with the Signature header.
Signature — verify authenticity
Every webhook includes a Signature header: the HMAC-SHA256 of the raw body, signed with your api_token (the same one you use as Bearer). So you don't manage any new key — you verify with your own api key.
Signature = hash_hmac('sha256', <raw_body>, <your_api_token>)$raw = file_get_contents('php://input'); // RAW body, not re-serialized
$expected = hash_hmac('sha256', $raw, $MY_API_TOKEN);
if (!hash_equals($expected, $_SERVER['HTTP_SIGNATURE'] ?? '')) {
http_response_code(401); exit; // invalid signature
}const crypto = require('crypto')
const raw = req.rawBody // RAW body (Buffer/string)
const expected = crypto.createHmac('sha256', MY_API_TOKEN).update(raw).digest('hex')
if (expected !== req.get('Signature')) return res.sendStatus(401)Sign the **raw** body
Compute the HMAC over the exact bytes you received, before parsing/re-serializing the JSON: re-encoding changes whitespace and key order, and the signature won't match.
Idempotency
On network errors delivery is retried: identify the request by its id in the body and always respond 2xx, even on duplicates, to avoid useless retries.
Result format by type
The result field (found in GET /api/scanner-request/{id}, and the same one delivered via socket and webhook) changes shape depending on the type of request. The main shapes are below, with real examples.
`result` vs `/results`
result= the full scan payload (properties, personal data, PDF…), ready to use.GET /api/scanner-request/{id}/results= a paginated view of the persisted properties only (with owners), handy when there are many. It applies to the types that list properties (catasto,tax-data,list-addresses,intestatari-catasto) and returns a Laravel paginator{ current_page, data: [...], links, ... }.
{
"has_soppressi": true,
"immobili": [
{
"foglio": "368", "particella": "435", "subalterno": "5",
"indirizzo": "VIA ESEMPIO n. 11 Interno 1 Piano S1 - T",
"comune": "ROMA", "provincia": "RM", "numero_civico": "11", "piano": "S1",
"categoria": "A02", "classe": "02", "consistenza": "6 vani", "rendita": "1239,50",
"catasto": "F", "codice_belfiore": "H501", "partita": "", "zona_cens": "004",
"scanner_immobili_id": 82659
},
{
"foglio": "368", "particella": "435", "subalterno": "24",
"indirizzo": "VIA ESEMPIO n. 19 Piano T",
"comune": "ROMA", "provincia": "RM", "numero_civico": "19", "piano": "T",
"categoria": "C01", "classe": "10", "consistenza": "46 m2", "rendita": "3594,44",
"catasto": "F", "codice_belfiore": "H501", "partita": "", "zona_cens": "004",
"scanner_immobili_id": 82678
}
]
}{
"intestatari": [
{
"nome_cognome": "ROSSI MARIO", "codice_fiscale": "RSSMRA80A01H501U",
"quota": "1/2", "titolarita": "Proprieta'",
"luogo_nascita": "ROMA", "data_nascita": "01/01/1980",
"comune": "ROMA", "provincia": "RM"
},
{
"nome_cognome": "ESEMPIO IMMOBILIARE SRL", "codice_fiscale": "01234567890",
"quota": "1/2", "titolarita": "Nuda proprieta'",
"luogo_nascita": "", "data_nascita": "",
"comune": "ROMA", "provincia": null
}
]
}{
"immobili": [],
"anagrafiche": [
{
"cognome": "ROSSI", "nome": "MARIO",
"data_di_nascita": "17/05/1980", "luogo_di_nascita": "ROMA (RM)",
"sesso": "M", "codice_fiscale": "RSSMRA80E17H501U"
}
]
}{
"addresses": {
"123494##VIA ROMA": " VIA ROMA ",
"337696##VIA ROMAGNOSI": " VIA ROMAGNOSI ",
"12428##VIALE ROMAGNA": " VIALE ROMAGNA "
}
}{
"pdf_path": "https://.../visure/<request_id>.pdf",
"request_id": "…",
"is_nota_negativa": false,
"is_empty": false
}Where to find each individual call's schema
The input parameters and the exact type of each query (foglio/particella, tax_code, tipo_visura, etc.) are on Swagger, Scanner section: api.dokicasa.it/api/documentation#/Scanner. This page instead covers how to receive and interpret the results.
How to test the integration
There is a sandbox environment — https://api-dev.dokicasa.it — that mirrors the production API but makes no real queries: every call returns realistic sample data, consumes no credit and produces no real documents. Compared to production, only the base URL changes: endpoints, parameters, response format, WebSocket and webhooks are identical.
🔑 Test API key (sandbox)
Use this token: it's a user with every call enabled and free, made specifically for testing. In sandbox the origin whitelist is disabled, so you can call from localhost or any test domain.
HfBfYShns3E02no3kkwCRWQugkVNcJCtrRN2BPoI5NxhqsiXaxxnSPpIHY2OSend it as the Authorization: Bearer <token> header on every call.
Already loaded in the Playground
In the interactive Playground this token is pre-filled (and copyable): open it, hit Run and watch socket + polling live, with zero setup.
Endpoints to test
API — base https://api-dev.dokicasa.it
| What | Endpoint |
|---|---|
| Start a search | POST /api/scanner/{type} → { scanner_request_id, status } |
| Status + result | GET /api/scanner-request/{id} · …/{id}/status |
| Paginated results | GET /api/scanner-request/{id}/results |
| Create monitoring | POST /api/monitoring/from-catasto |
| Simulate webhook (sandbox only) | POST /api/user-monitorings/{id}/simulate-webhook |
Socket — sandbox (see also Socket environments)
| Parameter | Value |
|---|---|
wsHost | api-dev.dokicasa.it |
key | staging |
authEndpoint | https://api-dev.dokicasa.it/broadcasting/auth |
| channel · event | private-scanner_request.{id} · ScannerRequestNotification |
Quick try (curl)
curl -X POST https://api-dev.dokicasa.it/api/scanner/list-addresses \
-H "Authorization: Bearer HfBfYShns3E02no3kkwCRWQugkVNcJCtrRN2BPoI5NxhqsiXaxxnSPpIHY2O" \
-H "Content-Type: application/json" \
-d '{"indirizzo":"via roma","comune":"Milano","provincia":"MI"}'
# → { "scanner_request_id": 12345, "status": "ENQUEUED" }
# then: GET /api/scanner-request/12345/status until status = SUCCESSWhat to verify:
- Async flow — the POST returns
ENQUEUED, then via socket (instant) or polling you reachSUCCESS. - Response shape — map the
resultfields (see Result format by type). - Webhook — point
webhook_urlat an inspector (webhook.site); in sandbox the monitoring sends a sample event immediately on creation (or re-trigger it with/simulate-webhook). - Signature — validate the
Signatureheader (see Signature section).
NOTE
In sandbox the data is representative and fixed (it does not vary with the input parameters): it shows you the result structure. When ready, switch to the production base URL and production socket (see the Socket environments table).
TIP
For your first tests use list-addresses or search-fiscal-code: they are fast and immediately show you the shape of result without needing specific cadastral data.
Property monitoring
Beyond one-off searches, you can monitor a property over time: Dokicasa periodically re-checks the cadastral owners and, when they change, sends you a webhook with the variation.
Start a monitoring
POST /api/monitoring/from-catasto — start straight from the cadastral data (no property id needed). If the property isn't in the system yet, it's first resolved with a cadastral search (owners included) and saved, then monitoring is activated.
{
"foglio": "123",
"particella": "45",
"subalterno": "7",
"comune": "Roma",
"provincia": "RM",
"webhook_url": "https://your-server.com/dokicasa/monitoring-changed"
}| Field | Required | Notes |
|---|---|---|
foglio, particella | ✅ | the cadastral "triplet" |
subalterno | — | numeric, optional |
comune | ✅ | cadastral code or town name |
provincia | ✅ | province code (no TN/BZ) |
search_catasto_type | — | F buildings (default) / T land |
sezione_comune | — | for towns with sections (Naples/Rome/Genoa) |
webhook_url | — | URL to receive changes (see below) |
name, note | — | free labels |
Response: { immobile_id, user_monitoring_id, created, scanned, last_monitoring, next_monitoring } (last_monitoring = last run, null if never; next_monitoring = next run).
Owners-change webhook
If you set webhook_url, on every owners change detected by the monitoring Dokicasa makes an async POST to that URL with the full change JSON:
{
"event": "immobile.owners_changed",
"user_monitoring_id": 12345,
"immobile": {
"id": 987, "foglio": "123", "particella": "45",
"subalterno": "7", "comune": "Roma", "provincia": "RM"
},
"changes": [
{
"date": "2026-07-01",
"differences": {
"added": [ { "quota": "1/2", "nome": "Mario", "cognome": "Rossi", "denominazione": null } ],
"removed": [ { "quota": "1/2", "nome": "Luigi", "cognome": "Verdi", "denominazione": null } ],
"modified": [ { "id": "CF:RSSMRA80A01H501U", "before": { "quota": "1/3" }, "after": { "quota": "1/2" } } ]
}
}
]
}NOTE
The webhook fires automatically from the monitoring cron, with retries on network errors. Respond 2xx to acknowledge receipt.
TIP
webhook_url is optional at creation: if you don't pass it, nothing is sent. You can add or change it later (see below).
Test the webhook in **sandbox** (staging)
On the test environment (https://api-dev.dokicasa.it) you don't have to wait for a real owners change:
- On creation, if you pass a
webhook_url, we send a sampleimmobile.owners_changedevent to that URL right away (the create response includessimulated_webhook_sent: true). - You can re-send it any time with
POST /api/user-monitorings/{id}/simulate-webhook.
The payload has the exact same format as the real event: point webhook_url to an inspector (webhook.site) to see it arrive. In production these helpers are disabled — the webhook only fires on a real owners change.
Listing your monitorings
GET /api/users/me/monitorings — returns your monitorings (paginated). Filters via query string:
| Parameter | Values | Notes |
|---|---|---|
filter.type | IMMOBILE (default) / SOGGETTO | monitoring type |
filter.status | ACTIVE, EXPIRED, CANCELLED | multiple allowed, comma-separated |
filter.source | API / CENSIMENTO | monitoring origin |
filter.change_owner | 1 | only those with owner changes |
filter.text | free text | searches notes + address/cadastral data |
sort_ends_at | ASC / DESC | sort by expiry (default: id DESC) |
page | number | page (paginated response) |
Need only the ids? GET /api/users/me/monitorings-ids.
NOTE
This is a read-only endpoint: it always works with your Bearer token, even without a configured origin whitelist. The default filter.type=IMMOBILE returns only property monitorings — for subjects pass filter.type=SOGGETTO.
Managing a monitoring
| Method | Path | What it does |
|---|---|---|
GET | /api/user-monitorings/{id} | Detail of the monitoring: monitoring data, linked property (model), current owners (model.soggetti), change events (events) and the owner reads history (proprietari_history). See example below. |
PATCH | /api/user-monitorings/{id}/webhook | Set or remove (pass null) the webhook_url. Body: { "webhook_url": "https://…" }. |
PATCH | /api/user-monitorings/{id} | Change status: ACTIVE, DISACTIVE (paused), CANCELED. |
POST | /api/user-monitorings/{id}/renew | Renew the monitoring. |
POST | /api/user-monitorings/{id}/simulate-webhook | Sandbox only: immediately sends a sample immobile.owners_changed event to the webhook_url, to test delivery without waiting for a real change. In production it returns 404. |
DELETE | /api/user-monitorings/{id} | Delete the monitoring. |
Example response — monitoring detail
{
"id": 6004,
"user_id": 5101,
"status": "ACTIVE",
"ends_at": "2026-07-10 11:08:01",
"model_type": "App\\Models\\ScannerImmobile",
"model_id": 2013602,
"model": {
"id": 2013602,
"comune": "Nardo'", "provincia": "LE",
"foglio": "129", "particella": "1474", "subalterno": "1",
"categoria": "A03",
"indirizzo_completo": "VIA CAVALIERI TEUTONICI n. 6 Piano T",
"soggetti": [
{
"denominazione": "COLELLA ANNA MARIA",
"codice_fiscale": "CLLNMR35P44F842Z",
"pivot": { "quota": "1/2", "titolarita": "Proprieta'" }
}
]
},
"events": [
{
"date": "2025-12-02",
"differences": {
"added": [ { "quota": "1/2", "denominazione": "BERNES ANGELO" } ],
"removed": [ { "quota": "", "denominazione": "AMATEIS DAVIDE" } ],
"modified": []
}
}
],
"proprietari_history": [
{
"date": "2025-11-02",
"read_at": "2025-11-02 00:00:00",
"scanner_request_id": 2071196,
"proprietari": [
{ "denominazione": "AMATEIS DAVIDE", "codice_fiscale": "MTSDVD82H03D208H", "quota": "", "titolarita": "" },
{ "denominazione": "COLELLA ANNA MARIA", "codice_fiscale": "CLLNMR35P44F842Z", "quota": "1/2", "titolarita": "Proprieta'" }
]
},
{
"date": "2025-12-02",
"read_at": "2025-12-02 00:00:00",
"scanner_request_id": 2241215,
"proprietari": [
{ "denominazione": "BERNES ANGELO", "quota": "1/2", "titolarita": "Proprieta'" },
{ "denominazione": "COLELLA ANNA MARIA", "quota": "1/2", "titolarita": "Proprieta'" }
]
}
]
}| Field | Contents |
|---|---|
model | Detail of the monitored property (municipality, sheet/parcel/sub, category, address…) |
model.soggetti | Current owners (with quota and titolarità in the pivot) |
events | Owner changes: diff per date (added / removed / modified) |
proprietari_history | Owner snapshot at every read (one entry per scanner_request, with date/read_at): the raw, non-deduplicated history of who the owners were at each check |
Use case: address search
You have an address and want the associated cadastral properties. Two phases: address resolution → properties.
list-addresses(address resolution) —POST /api/scanner/list-addresses. Wait via socket/webhook/polling. The normalized address list is already in theresultsfield ofGET /api/scanner-request/{id}— no need to call/results.- Properties from an address — use an address from step 1 as
address_valuein the properties scanner call (exact endpoint on Swagger, Scanner section). - Fetch results — when
SUCCESSarrives, callGET /api/scanner-request/{id}/resultsfor the property list.
Use case: visure, inspections, notes, maps (PDF)
All requests producing a PDF (cadastral visura, inspection, note, map) follow the same pattern.
- Create the request —
POST /api/scanner/{type}(type depends on the document, see Swagger). - Wait for completion — via WebSocket or webhook: state goes
ENQUEUED→SCANNINGup toSUCCESSorERROR. - Automatic retry on transient errors — if a temporary error occurs (line issues with external services), the system retries by itself every ~5 minutes. You don't need to resend anything. Credit is charged only on the first success.
- Get the PDF — the file is returned directly in the
GET /api/scanner-request/{id}response (no/results). The PDF is also sent automatically by email to the address bound to the token, so the end user gets it even without integrating the client-side download.
Polling — last resort only
Not recommended
You have two push mechanisms (WebSocket and webhook) that remove the need for polling. Consider them first: polling increases latency and server load with no real benefit.
If you really must poll, query periodically:
GET /api/scanner-request/{id}Practical guidelines:
- Recommended minimum interval: 3 seconds between calls.
- Exponential backoff after a few attempts (e.g. 3s → 6s → 12s).
- A sensible attempt limit (e.g. 5 minutes total), then time out on your side.
- Stop as soon as the state is
SUCCESSorERROR.
Typical response:
{ "id": 12345, "status": "SCANNING", "percentage": 42 }Operational notes
- Always keep the
id(scanner_request_id) returned at creation: you need it for every later operation. - Handle the
ERRORstate explicitly: read the message to understand the cause. - Never share the API token: treat it like a password.
- In production, prefer webhook or socket over polling.