---
title: "Receive discovery progress update from daemon"
method: POST
path: "/api/v1/discovery/{session_id}/update"
tags: ["Discoveries", "internal"]
---

# Receive discovery progress update from daemon

`POST /api/v1/discovery/{session_id}/update`

Internal endpoint for daemons to report discovery progress.

## Path parameters

- `session_id` string, uuid, required

## Request body

- DiscoveryUpdatePayload — Progress update from daemon to server during discovery
  - `daemon_id` string, uuid, required — The daemon this entity refers to.
  - `discovery_id` string, uuid, nullable — The discovery configuration this session belongs to. Always enriched server-side; daemons do not send this field.
  - `discovery_type` union, required
    - object
      - `host_id` string, uuid, required — The host the daemon is running on.
      - `type` 'SelfReport', required
    - object
      - `host_naming_fallback` 'Ip' | 'BestService', required
      - `snmp_credentials` object — SNMP credentials for querying devices during discovery Server builds this mapping before initiating discovery
      - `subnet_ids` string[], nullable, required — Subnets to sweep. `null` sweeps every subnet on the network.
      - `type` 'Network', required
    - object
      - `host_id` string, uuid, required — The host the daemon is running on.
      - `host_naming_fallback` 'Ip' | 'BestService', required
      - `type` 'Docker', required
    - object — A one-shot verification of a single host: re-check the addresses and ports already recorded for it, rather than sweeping a subnet. Created by the server only (never via the API) and deleted once its session reaches a terminal phase, so it is not a discovery configuration anyone owns or sees in their scan list.
      - `host_id` string, uuid, required — ID of the host that the daemon is running on — same meaning as every other variant. The host being rescanned is `target_host_id`.
      - `ips` string[], required — Addresses to scan on that host.
      - `ports` PortType[] — Ports already known on that host, re-checked to confirm they are still open. Scanned in addition to the standard discovery set, so a rescan also surfaces newly-opened services.
        - `number` integer, required — TCP or UDP port number
        - `protocol` 'Udp' | 'Tcp', required — Transport protocol the port is open on.
        - `type` 'Ssh' | 'Telnet' | 'DnsUdp' | 'DnsTcp' | 'Samba' | 'Nfs' | 'Ftp' | 'Ipp' | 'LdpTcp' | 'LdpUdp' | 'Ldap' | 'Ldaps' | 'Kerberos' | 'Snmp' | 'SnmpAlt' | 'Rdp' | 'Ntp' | 'Sip' | 'SipTls' | 'Rtsp' | 'Dhcp' | 'Http' | 'MySql' | 'PostgreSQL' | 'MongoDB' | 'Redis' | 'MsSql' | 'Docker' | 'DockerTls' | 'Kubernetes' | 'RabbitMqMgmt' | 'Cassandra' | 'Elasticsearch' | 'InfluxDb' | 'CouchDb' | 'Kafka' | 'Http3000' | 'Http5000' | 'Http8080' | 'Http8081' | 'Http8082' | 'Http8888' | 'Http9000' | 'Https' | 'Https8443' | 'Https9443' | 'Https10443' | 'Mqtt' | 'MqttTls' | 'AMQP' | 'AMQPTls' | 'Wireguard' | 'OpenVPN' | 'BACnet' | 'JetDirect' | 'Custom' — Well-known port identifier. Auto-derived from number+protocol, so it is optional on create.
      - `settings` RescanSettings — Scan settings that apply to a single-host rescan. Deliberately narrower than [`ScanSettings`]: a rescan verifies a known host against a known port set, so the full-scan mechanism (`is_full_scan`, `full_scan_interval`) must not be expressible — promoting a rescan to a 65,535-port sweep defeats the feature. The remaining omissions are settings that cannot bind on a one-or-two address target.
        - `arp_retries` integer, nullable — ARP retry rounds. Matters more here than in a sweep: for a rescan, "did it answer" is the entire answer, so a missed round reads as a dead host.
        - `port_scan_batch_size` integer, nullable — Ports scanned concurrently per host.
        - `probe_raw_socket_ports` boolean — Whether to probe raw-socket ports 9100-9107. Correctness-affecting: with this off the scanner drops those ports from its results, so a printer's known JetDirect port would look like it had disappeared.
        - `scan_rate_pps` integer, nullable — Port scan probes per second. Operators lower this for fragile devices or noisy IDS, and a rescan must respect that as much as a discovery does.
        - `use_npcap_arp` boolean — On Windows, use Npcap broadcast ARP instead of SendARP.
      - `target_host_id` string, uuid, required — The host being rescanned.
      - `type` 'Rescan', required
    - object
      - `host_id` string, uuid, required — ID of the host that the daemon is running on
      - `host_naming_fallback` 'Ip' | 'BestService', required
      - `scan_settings` ScanSettings — Scan performance settings. Lives on the discovery entity. Numeric fields are `Option<T>` — `None` means "use daemon default". The daemon unwraps with defaults at point of use.
        - `arp_rate_pps` integer, nullable — ARP packets per second (default: 50)
        - `arp_retries` integer, nullable — ARP retry rounds for non-responsive targets (default: 2 = 3 total attempts)
        - `arp_scan_cutoff` integer, nullable — ARP scan cutoff prefix. Interfaced subnets larger than this prefix are truncated to this many IPs. Default: 15 (= /15, ~131K IPs). Lower values scan more IPs — increase arp_rate_pps accordingly.
        - `full_scan_interval` integer, nullable — Run a full 65k port scan every N scans. Other scans use a light port set. Default: 3. Value of 0 means never full scan. Value of 1 means every scan is full.
        - `is_full_scan` boolean — Whether this specific scan run should do a full 65k port scan. Set by the server before dispatching to the daemon — not user-configurable.
        - `max_discovery_duration` integer, nullable — Hard ceiling on how long a single discovery run may take, in seconds (default: 21600 = 6h). When hit, the run force-completes and any hosts still queued are left un-scanned until the next run. Raise this for very large networks that legitimately need more than the default window.
        - `port_scan_batch_size` integer, nullable — Ports scanned concurrently per host (default: 200, clamped 16-1000)
        - `probe_raw_socket_ports` boolean — Whether to probe raw-socket ports 9100-9107 (default: false). Disabled by default to prevent ghost printing on JetDirect printers.
        - `scan_rate_pps` integer, nullable — Port scan probes per second (default: 500)
        - `use_npcap_arp` boolean — On Windows, use Npcap broadcast ARP instead of SendARP (default: false)
      - `subnet_ids` string[], nullable, required — Subnets to scan. None = scan all interfaced subnets.
      - `type` 'Unified', required
  - `error` string, nullable — Failure message, when the run did not complete.
  - `estimated_remaining_secs` integer, nullable — Rough estimate of the time left, in seconds.
  - `finished_at` string, date-time, nullable — When the run finished. `null` while it is still going.
  - `hosts_discovered` integer, nullable — Hosts found so far.
  - `network_id` string, uuid, required — The network this entity belongs to.
  - `phase` 'AwaitingSnapshot' | 'Queued' | 'Pending' | 'Starting' | 'Started' | 'Scanning' | 'Complete' | 'Failed' | 'Cancelled', required
  - `progress` integer, required — Completion of the current phase, from 0 to 1.
  - `scanned` ScannedEntityIds — Canonical IDs of entities scanned in a discovery session. Populated daemon-side at terminal phase from `EntityBuffer`'s `Created` entries. Travels with the terminal `DiscoveryUpdatePayload` to the server, rides the in-memory `EntityOperation::Created` event published for the historical Discovery row (the event scope carries `Entity::Discovery` with the full struct, including `run_type::Historical { results }`), then is stripped before persisting into the historical Discovery row's JSONB (see the `SqlValue::RunType` bind_value handler in `backend/src/server/shared/storage/generic.rs`). Per-entity-service subscribers extract `results.scanned` from the in-memory event and call `DiscoveryFkUpdater::update_discovery_fks` to backfill `last_discovery_id` / `first_discovery_id` on the matched rows. Naming: `scanned_*` because the daemon scans entities — some submissions match existing rows (refresh), others insert new rows. Both populate the EntityBuffer with canonical (server-assigned) IDs.
    - `binding_ids` string[] — Service bindings touched by this discovery.
    - `host_ids` string[] — Hosts touched by this discovery.
    - `interface_ids` string[] — Interfaces touched by this discovery.
    - `ip_address_ids` string[] — IP addresses touched by this discovery.
    - `port_ids` string[] — Ports touched by this discovery.
    - `service_ids` string[] — Services touched by this discovery.
    - `subnet_ids` string[] — Subnets touched by this discovery.
    - `vlan_ids` string[] — VLANs touched by this discovery.
  - `session_id` string, uuid, required — The discovery run this update belongs to.
  - `started_at` string, date-time, nullable — When the run started.
  - `warnings` string[] — Non-fatal warnings for a completed run (e.g. the scan hit its time limit and left hosts un-scanned). Unlike `error`, these do not mark the run failed.

## Response `200`

Update received

- ApiResponse
  - `data` TupleUnit — No payload. Present only so the envelope keeps its shape.
  - `error` string, nullable — Human-readable failure message. Omitted on success.
  - `meta` ApiMeta, required — API metadata included in all responses
    - `api_version` integer, required — API version (integer, increments on breaking changes)
    - `server_version` string, required — Server version (semver)
  - `success` boolean, required — `true` when the request succeeded. `false` responses carry `error` instead of `data`.

---

[API](https://skmtc.net/scanopy/apis/scanopy-api.md) · [All operations](https://skmtc.net/scanopy/apis/scanopy-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/scanopy/scanopy-api/versions/28e466341947/schema)
