---
title: "Bulk-create assets"
method: POST
path: "/v1/assets/bulk"
tags: ["Assets"]
---

# Bulk-create assets

`POST /v1/assets/bulk`

## Request

- Base URL: `https://api.merlonix.com`
- URL: `https://api.merlonix.com/v1/assets/bulk`
- Auth: HTTP bearer

Create up to 100 assets in one request — for onboarding a client estate programmatically. Requires a `write`-scoped key. The plan asset quota is enforced across the whole batch (the request is rejected with `402` if it would exceed the ceiling). Per-asset failures are reported in `errors` without aborting the batch; valid rows are still created. Returns `201` when at least one asset was created or revived, `207` when every row failed. TD-2480: a row whose hostname belongs to a previously-deleted asset RESTORES that asset (with its check history) and is reported in `revived` / `revived_count`, not in `created` — `skipped` now means only that an asset you can still see already has that hostname.

## Request body

- AssetBulkCreate: Bulk asset-creation request — 1 to 100 assets (TD-925).
  - `assets` AssetCreate[], required
    - `asset_type` 'domain' | 'hostname' | 'certificate' | 'dns_record', required
    - `hostname` string, required: The hostname / domain to monitor.
    - `attestation_id` string, uuid: Optional ownership attestation; must belong to the tenant.
    - `label` string: Optional human label.
    - `client_name` string
    - `client_id` string, uuid: Optional client bucket; defaults to Unassigned.
    - `check_interval_sec` integer: Check cadence. Clamped up to the plan minimum if faster than the tier allows.
    - `ssl_enabled` boolean
    - `dns_enabled` boolean
    - `domain_enabled` boolean: Opt into monitored domain-expiry (RDAP) checks.
    - `dnsbl_enabled` boolean: Opt into monitored DNS-blocklist (DNSBL) checks.
    - `dns_security_enabled` boolean: Opt into monitored DNSSEC + DANE/TLSA posture checks (informational).
    - `security_headers_enabled` boolean: Opt into the monitored HTTP security-header check (re-grades the six core headers on cadence + alerts when one disappears; informational).
    - `email_auth_enabled` boolean: Opt into the monitored email-auth (SPF/DMARC/MTA-STS/BIMI) posture check.
    - `ct_watch_enabled` boolean: Opt into Certificate-Transparency rogue/shadow-cert monitoring. Paid monitoring tiers only (team/agency/compliance).
    - `agent_readiness_enabled` boolean: Opt into monitored Agent-Readiness checks.
    - `mcp_health_enabled` boolean: Opt into monitored MCP-server health checks (discovers the MCP endpoint from /.well-known/mcp.json and runs a real JSON-RPC handshake + tools/list; alerts on downtime and tool/schema drift).
    - `heartbeat_enabled` boolean: Opt into the inbound heartbeat (dead-man's-switch) monitor. The ingest token is server-minted on first enable.
    - `heartbeat_period_sec` integer: Expected heartbeat interval in seconds.
    - `heartbeat_grace_sec` integer: Tolerance in seconds before a missed beat is declared dead.
    - `port_enabled` boolean: Opt into the outbound TCP-port monitor.
    - `port_check_port` integer: TCP port to probe on the asset's hostname.
    - `psi_enabled` boolean: Opt into the PageSpeed Insights (Lighthouse) performance monitor. Paid monitoring tiers only (team/agency/compliance).
    - `psi_check_url` string, uri: Optional override URL to measure with PageSpeed Insights (defaults to https://<hostname>).
    - `health_enabled` boolean: Opt into the application-health monitor (polls a JSON health endpoint). Paid monitoring tiers only (team/agency/compliance).
    - `health_check_url` string, uri: JSON health endpoint to poll (laravel-health / Oh Dear shape, with a generic fallback). SSRF-guarded.
    - `health_check_config` object: TD-941 application-health config. headers: auth header(s) sent with the GET (≤10, envelope headers rejected, values never logged); stale_after_sec: freshness window in seconds (30..86400, default 600).
      - `headers` object
      - `stale_after_sec` integer
    - `crawl_enabled` boolean: Opt into the broken-links / mixed-content crawler. Paid monitoring tiers only (team/agency/compliance).
    - `crawl_root_url` string, uri: Optional crawl root URL (defaults to https://<hostname>).
    - `injection_scan_enabled` boolean: Opt into the prompt-injection / hidden-text scan (computed-DOM render). Paid monitoring tiers only (team/agency/compliance).
    - `uptime_check_url` string, uri: Enables HTTP uptime checks when set (GET probe). For method/headers/body/redirect assertions use uptime_check_config.
    - `uptime_check_config` object: TD-935 structured uptime probe (wins over uptime_check_url). method=GET|HEAD|POST|PUT|PATCH; optional custom headers (≤20, forbidden envelope headers rejected), body (≤8KiB, body-bearing methods only), expected_status, keyword_match, and expected_final_url (redirect-target assertion — DOWN if the final URL after one redirect hop differs). Timing reported: ttfb_ms (headers received) and response_time_ms (total/body read). Cloudflare Workers fetch() exposes no DNS/TCP/TLS phase breakdown, so those are not reported.
      - `check_url` string, uri, required
      - `method` 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH'
      - `expected_status` integer
      - `keyword_match` string, nullable
      - `timeout_ms` integer
      - `headers` object, nullable
      - `body` string, nullable
      - `expected_final_url` string, uri, nullable
    - `vendor_dependencies` string[]

## Response `201`

Batch result (≥1 created).

- AssetBulkResult: Result of a bulk asset-creation request (TD-925).
  - `requested` integer, required: Number of assets in the request.
  - `created_count` integer, required
  - `error_count` integer, required
  - `skipped_count` integer, required: Rows skipped because the asset already existed (idempotent retry).
  - `created` Asset[], required
    - `id` string, uuid, required
    - `tenant_id` string, uuid, required
    - `attestation_id` string, uuid, nullable, required
    - `asset_type` 'domain' | 'hostname' | 'certificate' | 'dns_record', required
    - `hostname` string, required
    - `label` string, nullable, required
    - `client_name` string, nullable, required
    - `client_id` string, uuid, nullable, required
    - `check_interval_sec` integer, required
    - `ssl_enabled` boolean, required
    - `dns_enabled` boolean, required
    - `uptime_enabled` boolean, required
    - `domain_enabled` boolean, required
    - `dnsbl_enabled` boolean, required
    - `dns_security_enabled` boolean, required
    - `security_headers_enabled` boolean, required
    - `email_auth_enabled` boolean, required
    - `ct_watch_enabled` boolean, required
    - `agent_readiness_enabled` boolean, required
    - `mcp_health_enabled` boolean, required
    - `heartbeat_enabled` boolean, required
    - `heartbeat_period_sec` integer, nullable, required
    - `heartbeat_grace_sec` integer, nullable, required
    - `heartbeat_token` string, nullable, required
    - `port_enabled` boolean, required
    - `port_check_port` integer, nullable, required
    - `psi_enabled` boolean, required
    - `psi_check_url` string, nullable, required
    - `last_psi_at` string, date-time, nullable, required
    - `health_enabled` boolean, required
    - `health_check_url` string, nullable, required
    - `health_check_config` object, nullable, required: TD-941 application-health monitor config ({ headers?, stale_after_sec? }); null when not configured.
    - `crawl_enabled` boolean, required
    - `crawl_root_url` string, nullable, required
    - `last_crawl_at` string, date-time, nullable, required
    - `injection_scan_enabled` boolean, required
    - `injection_scan_last_at` string, date-time, nullable, required
    - `uptime_check_url` string, nullable, required
    - `uptime_check_config` object, nullable, required: TD-935 structured uptime probe config (method/headers/body/expected_status/keyword_match/expected_final_url); null when uptime is not configured.
    - `is_active` boolean, required
    - `paused_reason` string, nullable, required
    - `vendor_dependencies` string[], required
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
    - `last_checked_at` string, date-time, nullable, required
  - `errors` AssetBulkError[], required
    - `index` integer, required: Zero-based position in the request `assets` array.
    - `hostname` string: The hostname that failed (when known).
    - `error` string, required: Human-readable failure reason.
  - `skipped` AssetBulkSkipped[], required
    - `index` integer, required: Zero-based position in the request `assets` array.
    - `hostname` string, required: The hostname that already existed.
    - `reason` string, required: Always 'already_exists'.

## Other responses

- `207`: Batch result (every row failed).
- `400`: Error — canonical envelope with a trace_id.
- `401`: Error — canonical envelope with a trace_id.
- `402`: Error — canonical envelope with a trace_id.
- `403`: Error — canonical envelope with a trace_id.
- `429`: Error — canonical envelope with a trace_id.

---

[API](https://skmtc.dev/merlonix/apis/merlonix-public-api.md) · [All operations](https://skmtc.dev/merlonix/apis/merlonix-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/merlonix/apis/merlonix-public-api/revisions/2192584bdfcd?raw)
