---
title: "Create new Discovery"
method: POST
path: "/api/v1/discovery"
tags: ["Discoveries"]
---

# Create new Discovery

`POST /api/v1/discovery`

## Request body

- Discovery
  - `daemon_id` string, uuid, required
  - `discovery_type` union, required
    - object
      - `host_id` string, uuid, required
      - `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
      - `type` 'Network', required
    - object
      - `host_id` string, uuid, required
      - `host_naming_fallback` 'Ip' | 'BestService', required
      - `type` 'Docker', 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
  - `name` string, required
  - `network_id` string, uuid, required
  - `run_type` union, required
    - object
      - `cron_schedule` string, required
      - `enabled` boolean, required
      - `last_run` string, date-time, nullable
      - `timezone` string, nullable — IANA timezone for cron evaluation, e.g. "America/New_York". None = UTC.
      - `type` 'Scheduled', required
    - object — Historical discovery runs are created by the server and cannot be submitted via API
      - `results` DiscoveryUpdatePayload, required — Progress update from daemon to server during discovery
        - `daemon_id` string, uuid, required
        - `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
            - `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
            - `type` 'Network', required
          - object
            - `host_id` string, uuid, required
            - `host_naming_fallback` 'Ip' | 'BestService', required
            - `type` 'Docker', 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.
              - …
            - `subnet_ids` string[], nullable, required — Subnets to scan. None = scan all interfaced subnets.
            - `type` 'Unified', required
        - `error` string, nullable
        - `estimated_remaining_secs` integer, nullable
        - `finished_at` string, date-time, nullable
        - `hosts_discovered` integer, nullable
        - `network_id` string, uuid, required
        - `phase` 'AwaitingSnapshot' | 'Queued' | 'Pending' | 'Starting' | 'Started' | 'Scanning' | 'Complete' | 'Failed' | 'Cancelled', required
        - `progress` integer, required
        - `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[]
          - `host_ids` string[]
          - `interface_ids` string[]
          - `ip_address_ids` string[]
          - `port_ids` string[]
          - `service_ids` string[]
          - `subnet_ids` string[]
          - `vlan_ids` string[]
        - `session_id` string, uuid, required
        - `started_at` string, date-time, nullable
        - `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.
      - `type` 'Historical', required
    - object
      - `last_run` string, date-time, nullable
      - `type` 'AdHoc', required
  - `tags` string[], required
  - `created_at` string, date-time, required
  - `force_full_scan` boolean — When true, the next scan will be a full port scan regardless of interval
  - `id` string, uuid, required
  - `integration_targets` IntegrationTarget[], required — Per-daemon integration targeting: which integrations (credentialed or credential-less local) run on this daemon, and on which IPs. Delivered via the init command at registration and editable via the discovery modal. Persistent — re-applied every scan. This is the single home for cred↔IP targeting; it replaces the global `credential.target_ips` (race-prone, consumed once) and the discovery modal's old one-shot `pending_credential_ids`.
    - union — Per-daemon integration targeting, stored on the `Discovery` entity and delivered via the init command at registration. Each entry references exactly one stored credential and says where it applies on this daemon. This is the single home for cred↔IP targeting — it replaces the global, race-prone `credential.target_ips`. The variants ARE the scopes; their strum [`Target`] discriminants are the capability enum that `CredentialType::targets()` returns and validates against (single source of truth). Every target carries a real `credential_id` — there is no credential-less branch and no nil sentinel; a local socket is just a credential whose type targets only the daemon host.
      - object — The daemon's own host — realized as a 127.0.0.1 IP-override (e.g. a local Docker/Podman socket, or any credential the user pins to the daemon host without naming its IP).
        - `credential_id` string, uuid, required
        - `scope` 'DaemonHost', required
      - object — All hosts on the network — a broadcast default credential.
        - `credential_id` string, uuid, required
        - `scope` 'Network', required
      - object — Specific host IPs — one IP-override per address.
        - `credential_id` string, uuid, required
        - `ips` string[], required
        - `scope` 'Hosts', required
  - `scan_count` integer — Number of completed scans (incremented by server on session completion)
  - `updated_at` string, date-time, required

## Response `200`

Discovery created successfully

- ApiResponseDiscovery
  - `data` object
    - `daemon_id` string, uuid, required
    - `discovery_type` union, required
      - object
        - `host_id` string, uuid, required
        - `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
        - `type` 'Network', required
      - object
        - `host_id` string, uuid, required
        - `host_naming_fallback` 'Ip' | 'BestService', required
        - `type` 'Docker', 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
    - `name` string, required
    - `network_id` string, uuid, required
    - `run_type` union, required
      - object
        - `cron_schedule` string, required
        - `enabled` boolean, required
        - `last_run` string, date-time, nullable
        - `timezone` string, nullable — IANA timezone for cron evaluation, e.g. "America/New_York". None = UTC.
        - `type` 'Scheduled', required
      - object — Historical discovery runs are created by the server and cannot be submitted via API
        - `results` DiscoveryUpdatePayload, required — Progress update from daemon to server during discovery
          - `daemon_id` string, uuid, required
          - `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
              - …
            - object
              - …
            - object
              - …
            - object
              - …
          - `error` string, nullable
          - `estimated_remaining_secs` integer, nullable
          - `finished_at` string, date-time, nullable
          - `hosts_discovered` integer, nullable
          - `network_id` string, uuid, required
          - `phase` 'AwaitingSnapshot' | 'Queued' | 'Pending' | 'Starting' | 'Started' | 'Scanning' | 'Complete' | 'Failed' | 'Cancelled', required
          - `progress` integer, required
          - `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[]
            - `host_ids` string[]
            - `interface_ids` string[]
            - `ip_address_ids` string[]
            - `port_ids` string[]
            - `service_ids` string[]
            - `subnet_ids` string[]
            - `vlan_ids` string[]
          - `session_id` string, uuid, required
          - `started_at` string, date-time, nullable
          - `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.
        - `type` 'Historical', required
      - object
        - `last_run` string, date-time, nullable
        - `type` 'AdHoc', required
    - `tags` string[], required
    - `created_at` string, date-time, required
    - `force_full_scan` boolean — When true, the next scan will be a full port scan regardless of interval
    - `id` string, uuid, required
    - `integration_targets` IntegrationTarget[], required — Per-daemon integration targeting: which integrations (credentialed or credential-less local) run on this daemon, and on which IPs. Delivered via the init command at registration and editable via the discovery modal. Persistent — re-applied every scan. This is the single home for cred↔IP targeting; it replaces the global `credential.target_ips` (race-prone, consumed once) and the discovery modal's old one-shot `pending_credential_ids`.
      - union — Per-daemon integration targeting, stored on the `Discovery` entity and delivered via the init command at registration. Each entry references exactly one stored credential and says where it applies on this daemon. This is the single home for cred↔IP targeting — it replaces the global, race-prone `credential.target_ips`. The variants ARE the scopes; their strum [`Target`] discriminants are the capability enum that `CredentialType::targets()` returns and validates against (single source of truth). Every target carries a real `credential_id` — there is no credential-less branch and no nil sentinel; a local socket is just a credential whose type targets only the daemon host.
        - object — The daemon's own host — realized as a 127.0.0.1 IP-override (e.g. a local Docker/Podman socket, or any credential the user pins to the daemon host without naming its IP).
          - `credential_id` string, uuid, required
          - `scope` 'DaemonHost', required
        - object — All hosts on the network — a broadcast default credential.
          - `credential_id` string, uuid, required
          - `scope` 'Network', required
        - object — Specific host IPs — one IP-override per address.
          - `credential_id` string, uuid, required
          - `ips` string[], required
          - `scope` 'Hosts', required
    - `scan_count` integer — Number of completed scans (incremented by server on session completion)
    - `updated_at` string, date-time, required
  - `error` string, nullable
  - `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

## Other responses

- `400` — Can't create historical discovery

## Changes

- **2026-07-20** `9f94dcfb4631` — 6 info
  - added the new optional request property `allOf[#/components/schemas/DiscoveryBase]/discovery_type/oneOf[subschema #4: Unified]/scan_settings/max_discovery_duration`
  - added the new optional request property `allOf[#/components/schemas/DiscoveryBase]/run_type/oneOf[subschema #2: Historical]/results/discovery_type/oneOf[subschema #4: Unified]/scan_settings/max_discovery_duration`
  - added the new optional request property `allOf[#/components/schemas/DiscoveryBase]/run_type/oneOf[subschema #2: Historical]/results/warnings`
  - added the optional property `data/allOf[#/components/schemas/DiscoveryBase]/discovery_type/oneOf[subschema #4: Unified]/scan_settings/max_discovery_duration` to the response with the `200` status
  - …2 more
- **2026-06-29** `ee1e9482f96c` — 2 breaking, 2 info
  - removed `subschema #1, subschema #2` from the `allOf[subschema #2]/integration_targets/items/` request property `oneOf` list
  - added `subschema #1, subschema #2, subschema #3` to the `data/allOf[subschema #2]/integration_targets/items/` response property `oneOf` list for the response status `200`
  - added `subschema #1, subschema #2, subschema #3` to the `allOf[subschema #2]/integration_targets/items/` request property `oneOf` list
  - removed `subschema #1, subschema #2` from the `data/allOf[subschema #2]/integration_targets/items/` response property `oneOf` list for the response status `200`
- …earlier changes not shown

[Full history](https://skmtc.dev/scanopy/apis/scanopy-api/changes/api/v1/discovery/post.md)

---

[API](https://skmtc.dev/scanopy/apis/scanopy-api.md) · [All operations](https://skmtc.dev/scanopy/apis/scanopy-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/scanopy/scanopy-api/revisions/c037832cd07f/schema)
