---
title: "Provision a daemon, or re-provision an existing one"
method: POST
path: "/api/v1/daemons/provision"
tags: ["internal", "Daemons"]
---

# Provision a daemon, or re-provision an existing one

`POST /api/v1/daemons/provision`

Creates a daemon record on the server before the daemon is installed, mints an API key bound
to it 1:1, and returns ready-to-run install artifacts.

When `daemon_id` is supplied the existing record is reused instead of creating a new one —
this both re-issues install artifacts after install config changes and gives a legacy daemon
(one with no bound key) a pathway to a dedicated key without losing its host, discovery jobs,
or history.

Returns the daemon record and an API key that must be configured on the daemon.

## Request body

- ProvisionDaemonRequest — Request to pre-provision a daemon (either mode) before it is installed. This creates the daemon record + its 1:1 API key on the server so the install command shrinks to two flags.
  - `daemon_id` string, uuid, nullable — Mint a fresh 1:1 key for this existing daemon instead of creating a new record, keeping its host, discovery jobs and history. Used to give a legacy daemon (no bound key) a dedicated one. When set, `name`/`network_id`/`mode`/`url` are ignored — those come from the existing record. Only accepted for a daemon that has never checked in or has no bound key; a live provisioned daemon is refused, since it has no way to learn the new key. Note: install commands are not generated here — call the install-command endpoint, which builds them idempotently and fills in the key this response returns.
  - `mode` 'server_poll' | 'daemon_poll' — Daemon operating mode that determines the communication pattern. - **DaemonPoll** (formerly "Pull"): Daemon makes outbound connections to the server. The daemon registers itself and polls for work. Best for daemons behind NAT/firewall. - **ServerPoll** (formerly "Push"): Server makes connections to the daemon. Server polls daemon for status and discovery results. Best for DMZ deployments where daemon cannot make outbound connections.
  - `name` string, nullable — Human-readable name for the daemon. Required unless `daemon_id` is set, in which case the existing record's name is kept.
  - `network_id` string, uuid, nullable — Network this daemon will be associated with. Required unless `daemon_id` is set, in which case the existing record's network is kept.
  - `seed_credential_refs` IntegrationTarget[] — Credential/integration references to seed onto the daemon's first discovery run. References only — never secret material. Empty by default.
    - 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
  - `url` string, nullable — Reachable URL where the *server* can dial the daemon. Required for ServerPoll, unused for DaemonPoll (the daemon dials out instead).

## Response `201`

Daemon provisioned successfully

- ApiResponseProvisionDaemonResponse
  - `data` object — Response from provisioning a daemon. Contains the daemon record and the API key (shown only once). Install commands are deliberately not here — fetch them from the install-command endpoint, which builds them idempotently and fills in this key. That keeps a display-only regenerate (advanced-setting change, OS switch) from re-minting the key.
    - `daemon` DaemonResponse, required — Daemon response for UI including computed version status
      - `api_key_id` string, uuid, nullable — Foreign key to API key used for ServerPoll authentication. NULL for DaemonPoll daemons or those not yet linked to a key.
      - `host_id` string, uuid, required
      - `is_unreachable` boolean — Whether the daemon is unreachable (for ServerPoll circuit breaker). Set to true after repeated polling failures, reset via retry-connection endpoint.
      - `last_seen` string, date-time, nullable — Timestamp of last successful contact with daemon. NULL for provisioned ServerPoll daemons that haven't been contacted yet.
      - `mode` 'server_poll' | 'daemon_poll', required — Daemon operating mode that determines the communication pattern. - **DaemonPoll** (formerly "Pull"): Daemon makes outbound connections to the server. The daemon registers itself and polls for work. Best for daemons behind NAT/firewall. - **ServerPoll** (formerly "Push"): Server makes connections to the daemon. Server polls daemon for status and discovery results. Best for DMZ deployments where daemon cannot make outbound connections.
      - `name` string, required
      - `network_id` string, uuid, required
      - `standby` boolean — Whether the daemon is on standby due to inactivity (no discovery in 30 days).
      - `standby_cleared_at` string, date-time, nullable — Timestamp of the most recent standby → active transition. Set by `process_startup` when a restarted daemon is un-standby'd, and by the discovery auto-wake path. The nightly inactivity check skips daemons within the grace window (see `STANDBY_GRACE_PERIOD_DAYS`) to prevent the "restart → cleared → re-standby'd before discovery runs" race.
      - `tags` string[], required
      - `url` string, required — Address the *server* dials for a ServerPoll daemon. Editable (a daemon can move); unused and not editable for DaemonPoll, which dials out instead.
      - `user_id` string, uuid, required — User responsible for maintaining this daemon
      - `version` string, nullable — Daemon software version (semver format)
      - `created_at` string, date-time, required
      - `id` string, uuid, required
      - `interfaced_subnet_ids` string[], required — Subnets this daemon has interfaces on, loaded from the `daemon_interfaced_subnets` junction (replaces the old `capabilities.interfaced_subnet_ids` JSONB field).
      - `updated_at` string, date-time, required
      - `version_status` DaemonVersionStatus, required — Daemon version status including health and any warnings
        - `has_correct_docker_volume_mount` boolean
        - `status` 'Current' | 'Outdated' | 'Deprecated' | 'Unsupported' | 'Unknown', required — Health status for daemon versions. Lifecycle order: `Current` → `Outdated` → `Deprecated` → `Unsupported`, with `Unknown` for daemons whose version the server has no record of.
        - `sunset_date` string, nullable — The date this daemon's version stops being supported, if a sunset is scheduled for it. Surfaced top-level (not only inside `warnings`) so the UI can render a countdown from the same value the email uses.
        - `supports_targeted_rescan` boolean — Whether this daemon can run a single-host rescan. Server-computed so the frontend never has to hardcode a version floor.
        - `supports_unified_discovery` boolean
        - `version` string, nullable
        - `warnings` DeprecationWarning[]
          - `message` string, required
          - `severity` 'Info' | 'Warning' | 'Critical' | 'Unknown', required — Severity level for deprecation warnings
          - `sunset_date` string, nullable
    - `daemon_api_key` string, required — The API key (plaintext) for daemon authentication. This is shown only once - store it securely.
  - `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` — Invalid request
- `403` — Forbidden
- `409` — Daemon is live and already has a bound key

## Changes

- **2026-07-28** `1be6783504b4` — 1 info
  - added the optional property `data/daemon/allOf[subschema #2]/version_status/supports_targeted_rescan` to the response with the `201` status
- **2026-07-27** `823f4defed69` — 2 warning, 1 info
  - added the new `Unknown` enum value to the `data/daemon/allOf[subschema #2]/version_status/status` response property for the response status `201`
  - added the new `Unsupported` enum value to the `data/daemon/allOf[subschema #2]/version_status/status` response property for the response status `201`
  - added the optional property `data/daemon/allOf[subschema #2]/version_status/sunset_date` to the response with the `201` status
- **2026-07-21** `204a22d337e7` — 1 breaking, 1 warning
  - removed the required property `data/install_artifacts` from the response with the `201` status
  - removed the request property `install_config`
- **2026-07-20** `d4ed9256b072` — 1 info
  - added the optional property `data/install_artifacts/docker_compose` to the response with the `201` status
- **2026-07-20** `9f94dcfb4631` — 13 info
  - added the new optional request property `daemon_id`
  - added the new optional request property `install_config`
  - added the new optional request property `mode`
  - added the new optional request property `seed_credential_refs`
  - …9 more

[Full history](https://skmtc.dev/scanopy/apis/scanopy-api/changes/api/v1/daemons/provision/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/49ea97c8bb7e/schema)
