> For the complete documentation index see https://domscan.net/llms.txt. Markdown versions of every docs page are available by appending .md to the page URL.

Source: https://domscan.net/docs/watchlist

# Domain Watchlist

DomScan API: Monitor domains for expiration and availability changes. Get alerts when registered domains become available.

Monitor domains for expiration and availability changes. Get alerts when registered domains become available.

**Monitor**

Sign in with Google to access your personalized domain watchlist with email alerts, webhooks, and more.

Receive notifications via Email, Telegram, Slack, Discord, or Webhook.

[Monitor](https://domscan.net/monitor)
[Settings](https://domscan.net/monitor/settings)
[Brand Monitor](https://domscan.net/brand-monitor-api)

GET /v1/watchlist

Get all domains in your watchlist.

#### Example response

```json
{
                "watchlist": [
                {
                "domain": "startup.com",
                "added_at": "2024-01-15T10:30:00Z",
                "status": "registered",
                "expiry_date": "2025-01-15T00:00:00Z",
                "days_until_expiry": 287,
                "last_checked": "2024-03-01T12:00:00Z",
                "notify_email": "alerts@example.com",
                "check_count": 4
                }
                ],
                "total": 1,
                "updated_at": "2024-03-01T12:00:00Z"
                }
```

#### Response fields

| Field | Type |
| --- | --- |
| `has_more` | boolean |
| `next_cursor` | string \| null |
| `watchlist[]` | object[] |
| `watchlist[]` | object |
| `watchlist[].domain` | string |
| `watchlist[].added_at` | string |
| `watchlist[].status` | string |
| `watchlist[].last_checked` | string |
| `watchlist[].expiry_date` | string |
| `watchlist[].days_until_expiry` | integer |
| `watchlist[].notify_email` | string |
| `watchlist[].check_count` | integer |
| `total` | integer |
| `updated_at` | string |

POST /v1/watchlist

Add a domain to your watchlist.

#### Request body

```json
{
                "domain": "mydomain.com",
                "notify_email": "alerts@example.com"
                }
```

#### Response fields

| Field | Type |
| --- | --- |
| `success` | boolean |
| `message` | string |
| `watchlist[]` | object[] |
| `watchlist[]` | object |
| `watchlist[].domain` | string |
| `watchlist[].added_at` | string |
| `watchlist[].status` | string |
| `watchlist[].last_checked` | string |
| `watchlist[].expiry_date` | string |
| `watchlist[].days_until_expiry` | integer |
| `watchlist[].notify_email` | string |
| `watchlist[].check_count` | integer |
| `total` | integer |

#### Example response

```json
{
  "success": true,
  "message": "Added example.org to watchlist",
  "watchlist": [
    {
      "domain": "example.com",
      "added_at": "2026-06-02T09:12:00.000Z",
      "last_checked": "2026-10-09T03:00:12.000Z",
      "status": "expiring_soon",
      "expiry_date": "2026-12-18T14:22:08Z",
      "days_until_expiry": 70,
      "notify_email": "alerts@example.com"
    },
    {
      "domain": "example.org",
      "added_at": "2026-10-09T10:15:30.000Z",
      "status": "unknown",
      "notify_email": "alerts@example.com"
    }
  ],
  "total": 2
}
```

DELETE /v1/watchlist?domain=mydomain.com

Remove from watchlist

```bash
curl -X DELETE -H "X-API-Key: your-api-key" "https://domscan.net/v1/watchlist?domain=mydomain.com"
```

#### Response fields

| Field | Type |
| --- | --- |
| `success` | boolean |
| `message` | string |
| `domain` | string |
| `watchlist[]` | object[] |
| `watchlist[]` | object |
| `watchlist[].domain` | string |
| `watchlist[].added_at` | string |
| `watchlist[].status` | string |
| `watchlist[].last_checked` | string |
| `watchlist[].expiry_date` | string |
| `watchlist[].days_until_expiry` | integer |
| `watchlist[].notify_email` | string |
| `watchlist[].check_count` | integer |
| `total` | integer |

#### Example response

```json
{
  "success": true,
  "message": "Request completed",
  "domain": "example.com",
  "watchlist": [
    {
      "domain": "example.com",
      "added_at": "2026-08-27T12:00:00Z",
      "status": "registered",
      "last_checked": "2026-08-27T12:00:00Z",
      "expiry_date": "2026-08-27T12:00:00Z",
      "days_until_expiry": 1,
      "notify_email": "user@example.com",
      "check_count": 1
    }
  ],
  "total": 1
}
```

GET /v1/watchlist/expiring?days=30

Expiring soon

#### Example response

```json
{
                "expiring_domains": [
                {
                "domain": "renewal-window.com",
                "status": "expiring_soon",
                "expiry_date": "2026-05-12T00:00:00Z",
                "days_until_expiry": 28,
                "check_count": 11
                }
                ],
                "total": 1,
                "threshold_days": 30
                }
```

#### Response fields

| Field | Type |
| --- | --- |
| `has_more` | boolean |
| `next_cursor` | string \| null |
| `expiring_domains[]` | object[] |
| `expiring_domains[]` | object |
| `expiring_domains[].domain` | string |
| `expiring_domains[].added_at` | string |
| `expiring_domains[].status` | string |
| `expiring_domains[].last_checked` | string |
| `expiring_domains[].expiry_date` | string |
| `expiring_domains[].days_until_expiry` | integer |
| `expiring_domains[].notify_email` | string |
| `expiring_domains[].check_count` | integer |
| `total` | integer |
| `threshold_days` | integer |

GET /v1/watchlist/available

Available

Paginated: send `limit` (1 to 100) and pass `next_cursor` back as `cursor`. Responses include `has_more` and `next_cursor`. Without `limit` the whole list is returned. See [pagination](https://domscan.net/docs/response-format#pagination).

#### Example response

```json
{
                "available_domains": [
                {
                "domain": "startupdrop.ai",
                "status": "available",
                "last_checked": "2026-04-14T08:30:00Z",
                "check_count": 18
                }
                ],
                "total": 1,
                "message": "These domains from your watchlist are now available for registration!"
                }
```

#### Response fields

| Field | Type |
| --- | --- |
| `has_more` | boolean |
| `next_cursor` | string \| null |
| `available_domains[]` | object[] |
| `available_domains[]` | object |
| `available_domains[].domain` | string |
| `available_domains[].added_at` | string |
| `available_domains[].status` | string |
| `available_domains[].last_checked` | string |
| `available_domains[].expiry_date` | string |
| `available_domains[].days_until_expiry` | integer |
| `available_domains[].notify_email` | string |
| `available_domains[].check_count` | integer |
| `total` | integer |
| `message` | string |

### Alert webhooks

Receive monitoring alerts as signed JSON POST requests on your own HTTPS endpoints. Manage endpoints with an API key, or from Monitor settings while signed in.

#### Event types

| Monitor | Events |
| --- | --- |
| Watchlist availability, expiry and status | `domain.available`, `domain.expiring_30`, `domain.expiring_14`, `domain.expiring_7`, `domain.expiring_1`, `domain.status_change` |
| Domain change monitoring | `domain.dns_changed`, `domain.certificate_changed`, `domain.registration_changed`, `domain.hosting_changed`, `domain.technology_changed`, `domain.email_security_changed` |
| Brand monitoring (new lookalike registrations) | `brand.threat_detected` |
| Watchlist alert subscriptions (certificates, prices, DNS records, lookalikes) | `ssl.expiring`, `ssl.expired`, `ssl.changed`, `price.dropped`, `dns.changed`, `typosquat.detected` |
| Async jobs (free, only for jobs created with notify_webhook) | `job.completed`, `job.failed`, `job.cancelled` |
| Test event (free) | `webhook.test` |

[Read the webhook alerts API guide](https://domscan.net/webhook-alerts-api)

#### Pricing

Each alert delivered to an endpoint with a 2xx response costs 2 credits. Credits are reserved just before the first attempt and refunded if the delivery fails, is cancelled or the endpoint is deleted. Test events are free. When the balance is too low, the alert is skipped and nothing is charged.

POST /v1/webhooks

Register an HTTPS endpoint. The URL must resolve to a public address. Omit events, or pass ["*"], to receive every event type. The signing secret is returned only in this response.

#### Example request

```bash
curl -X POST "https://domscan.net/v1/webhooks" \
  -H "X-API-Key: $DOMSCAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/domscan",
    "events": ["domain.expiring_30", "domain.available", "domain.dns_changed"]
  }'
```

#### Example response

```json
{
  "endpoint": {
    "id": "whe_3f1c0a9b8e7d6c5b4a39281706f5e4d3",
    "url": "https://hooks.example.com/domscan",
    "events": ["domain.available", "domain.expiring_30", "domain.dns_changed"],
    "status": "active",
    "secret_hint": "9c2e"
  },
  "secret": "whsec_..."
}
```

#### Response fields

| Field | Type |
| --- | --- |
| `endpoint` | object |
| `endpoint.id` | string |
| `endpoint.url` | string |
| `endpoint.description` | string \| null |
| `endpoint.events[]` | string[] |
| `endpoint.status` | string |
| `endpoint.disabled_reason` | string \| null |
| `endpoint.consecutive_failures` | integer |
| `endpoint.secret_hint` | string |
| `endpoint.previous_secret_expires_at` | string \| null |
| `endpoint.secret_rotated_at` | string \| null |
| `endpoint.last_success_at` | string \| null |
| `endpoint.last_failure_at` | string \| null |
| `endpoint.last_error` | string \| null |
| `endpoint.created_at` | string |
| `endpoint.updated_at` | string |
| `endpoint.disabled_at` | string \| null |
| `secret` | string |

GET /v1/webhooks

List your endpoints with their status, subscribed events, failure count and the last four characters of the secret, together with the available event types and the delivery policy.

#### Response fields

| Field | Type |
| --- | --- |
| `endpoints[]` | object[] |
| `endpoints[]` | object |
| `endpoints[].id` | string |
| `endpoints[].url` | string |
| `endpoints[].description` | string \| null |
| `endpoints[].events[]` | string[] |
| `endpoints[].status` | string |
| `endpoints[].disabled_reason` | string \| null |
| `endpoints[].consecutive_failures` | integer |
| `endpoints[].secret_hint` | string |
| `endpoints[].previous_secret_expires_at` | string \| null |
| `endpoints[].secret_rotated_at` | string \| null |
| `endpoints[].last_success_at` | string \| null |
| `endpoints[].last_failure_at` | string \| null |
| `endpoints[].last_error` | string \| null |
| `endpoints[].created_at` | string |
| `endpoints[].updated_at` | string |
| `endpoints[].disabled_at` | string \| null |
| `available_events[]` | string[] |
| `all_events` | string |
| `test_event` | string |
| `policy` | object |
| `policy.secret_rotation_grace_seconds` | integer |
| `policy.secret_rotation_credits` | integer |
| `policy.credits_per_delivered_alert` | integer |
| `policy.test_event_credits` | integer |
| `policy.job_event_credits` | integer |
| `policy.failed_deliveries_charged` | boolean |
| `policy.max_endpoints` | integer |
| `policy.max_attempts` | integer |
| `policy.disable_after_consecutive_failures` | integer |
| `policy.signature_tolerance_seconds` | integer |

#### Example response

```json
{
  "endpoints": [
    {
      "id": "example-id",
      "url": "https://example.com",
      "events": [
        "example"
      ],
      "status": "active",
      "secret_hint": "example",
      "created_at": "2026-08-27T12:00:00Z",
      "description": "example",
      "disabled_reason": "customer",
      "consecutive_failures": 1,
      "previous_secret_expires_at": "2026-08-27T12:00:00Z",
      "secret_rotated_at": "2026-08-27T12:00:00Z",
      "last_success_at": "2026-08-27T12:00:00Z",
      "last_failure_at": "2026-08-27T12:00:00Z",
      "last_error": "example",
      "updated_at": "2026-08-27T12:00:00Z",
      "disabled_at": "2026-08-27T12:00:00Z"
    }
  ],
  "available_events": [
    "domain.available"
  ],
  "policy": {
    "credits_per_delivered_alert": 2,
    "test_event_credits": 0,
    "failed_deliveries_charged": false,
    "max_endpoints": 1,
    "max_attempts": 1,
    "disable_after_consecutive_failures": 1,
    "signature_tolerance_seconds": 1,
    "secret_rotation_grace_seconds": 86400,
    "secret_rotation_credits": 0,
    "job_event_credits": 0
  },
  "all_events": "*",
  "test_event": "webhook.test"
}
```

PATCH /v1/webhooks/:webhook_id

Pause or enable an endpoint, change its events or edit its description. Enabling an endpoint resets its failure count.

```json
{ "enabled": true, "events": ["*"] }
```

#### Response fields

| Field | Type |
| --- | --- |
| `endpoint` | object |
| `endpoint.id` | string |
| `endpoint.url` | string |
| `endpoint.description` | string \| null |
| `endpoint.events[]` | string[] |
| `endpoint.status` | string |
| `endpoint.disabled_reason` | string \| null |
| `endpoint.consecutive_failures` | integer |
| `endpoint.secret_hint` | string |
| `endpoint.previous_secret_expires_at` | string \| null |
| `endpoint.secret_rotated_at` | string \| null |
| `endpoint.last_success_at` | string \| null |
| `endpoint.last_failure_at` | string \| null |
| `endpoint.last_error` | string \| null |
| `endpoint.created_at` | string |
| `endpoint.updated_at` | string |
| `endpoint.disabled_at` | string \| null |

#### Example response

```json
{
  "endpoint": {
    "id": "example-id",
    "url": "https://example.com",
    "events": [
      "example"
    ],
    "status": "active",
    "secret_hint": "example",
    "created_at": "2026-08-27T12:00:00Z",
    "description": "example",
    "disabled_reason": "customer",
    "consecutive_failures": 1,
    "previous_secret_expires_at": "2026-08-27T12:00:00Z",
    "secret_rotated_at": "2026-08-27T12:00:00Z",
    "last_success_at": "2026-08-27T12:00:00Z",
    "last_failure_at": "2026-08-27T12:00:00Z",
    "last_error": "example",
    "updated_at": "2026-08-27T12:00:00Z",
    "disabled_at": "2026-08-27T12:00:00Z"
  }
}
```

DELETE /v1/webhooks/:webhook_id

Delete an endpoint. Pending deliveries are cancelled and any reserved credits are refunded.

#### Response fields

| Field | Type |
| --- | --- |
| `id` | string |
| `deleted` | boolean |

#### Example response

```json
{
  "id": "example-id",
  "deleted": true
}
```

POST /v1/webhooks/:webhook_id/test

Send one free webhook.test event and return the result. Test events are not retried and do not count toward automatic pausing.

#### Response fields

| Field | Type |
| --- | --- |
| `delivery` | object |
| `delivery.id` | string |
| `delivery.endpoint_id` | string |
| `delivery.event_id` | string |
| `delivery.event_type` | string |
| `delivery.status` | string |
| `delivery.attempts` | integer |
| `delivery.next_attempt_at` | string \| null |
| `delivery.last_attempt_at` | string \| null |
| `delivery.response_status` | integer \| null |
| `delivery.last_error` | string \| null |
| `delivery.credits_charged` | integer |
| `delivery.refunded` | boolean |
| `delivery.created_at` | string |
| `delivery.delivered_at` | string \| null |
| `delivered` | boolean |

#### Example response

```json
{
  "delivery": {
    "id": "example-id",
    "endpoint_id": "example",
    "event_id": "example",
    "event_type": "domain",
    "status": "pending",
    "attempts": 1,
    "next_attempt_at": "2026-08-27T12:00:00Z",
    "last_attempt_at": "2026-08-27T12:00:00Z",
    "response_status": 1,
    "last_error": "example",
    "credits_charged": 1,
    "refunded": false,
    "created_at": "2026-08-27T12:00:00Z",
    "delivered_at": "2026-08-27T12:00:00Z"
  },
  "delivered": false
}
```

POST /v1/webhooks/:webhook_id/rotate-secret

Replace the signing secret of an endpoint, including a paused one. The new secret is returned only in this response. For the next 24 hours each delivery is signed with both the new and the previous secret. Rotating is free.

#### Example request

```bash
curl -X POST "https://domscan.net/v1/webhooks/whe_3f1c0a9b8e7d6c5b4a39281706f5e4d3/rotate-secret" \
  -H "X-API-Key: $DOMSCAN_API_KEY"
```

#### Example response

```json
{
  "endpoint": {
    "id": "whe_3f1c0a9b8e7d6c5b4a39281706f5e4d3",
    "status": "active",
    "secret_hint": "4b7a",
    "previous_secret_expires_at": "2026-10-11T12:00:00.000Z"
  },
  "secret": "whsec_...",
  "previous_secret_expires_at": "2026-10-11T12:00:00.000Z",
  "grace_period_seconds": 86400
}
```

#### Response fields

| Field | Type |
| --- | --- |
| `endpoint` | object |
| `endpoint.id` | string |
| `endpoint.url` | string |
| `endpoint.description` | string \| null |
| `endpoint.events[]` | string[] |
| `endpoint.status` | string |
| `endpoint.disabled_reason` | string \| null |
| `endpoint.consecutive_failures` | integer |
| `endpoint.secret_hint` | string |
| `endpoint.previous_secret_expires_at` | string \| null |
| `endpoint.secret_rotated_at` | string \| null |
| `endpoint.last_success_at` | string \| null |
| `endpoint.last_failure_at` | string \| null |
| `endpoint.last_error` | string \| null |
| `endpoint.created_at` | string |
| `endpoint.updated_at` | string |
| `endpoint.disabled_at` | string \| null |
| `secret` | string |
| `previous_secret_expires_at` | string |
| `grace_period_seconds` | integer |

GET /v1/webhooks/:webhook_id/deliveries

#### Query parameters

| Parameter | Type | Description |
| --- | --- | --- |
| limit | integer | Number of deliveries to return, from 1 to 100. Default 50. |

List recent deliveries with their status, attempts, HTTP response, credits charged and refunds. Delivery records are kept for 30 days.

#### Response fields

| Field | Type |
| --- | --- |
| `endpoint_id` | string |
| `deliveries[]` | object[] |
| `deliveries[]` | object |
| `deliveries[].id` | string |
| `deliveries[].endpoint_id` | string |
| `deliveries[].event_id` | string |
| `deliveries[].event_type` | string |
| `deliveries[].status` | string |
| `deliveries[].attempts` | integer |
| `deliveries[].next_attempt_at` | string \| null |
| `deliveries[].last_attempt_at` | string \| null |
| `deliveries[].response_status` | integer \| null |
| `deliveries[].last_error` | string \| null |
| `deliveries[].credits_charged` | integer |
| `deliveries[].refunded` | boolean |
| `deliveries[].created_at` | string |
| `deliveries[].delivered_at` | string \| null |

#### Example response

```json
{
  "endpoint_id": "example",
  "deliveries": [
    {
      "id": "example-id",
      "endpoint_id": "example",
      "event_id": "example",
      "event_type": "domain",
      "status": "pending",
      "attempts": 1,
      "next_attempt_at": "2026-08-27T12:00:00Z",
      "last_attempt_at": "2026-08-27T12:00:00Z",
      "response_status": 1,
      "last_error": "example",
      "credits_charged": 1,
      "refunded": false,
      "created_at": "2026-08-27T12:00:00Z",
      "delivered_at": "2026-08-27T12:00:00Z"
    }
  ]
}
```

#### Request format

Each alert is a POST request with a JSON body. The id field stays the same for an event and is repeated in X-DomScan-Event-Id, so you can ignore retries you have already processed.

```http
POST /domscan HTTP/1.1
Content-Type: application/json
X-DomScan-Event: domain.expiring_30
X-DomScan-Event-Id: evt_6f1d2c3b4a5948e7b6c5d4e3f2a1b0c9
X-DomScan-Timestamp: 1791633600
X-DomScan-Signature: sha256=...

{
  "id": "evt_6f1d2c3b4a5948e7b6c5d4e3f2a1b0c9",
  "type": "domain.expiring_30",
  "created": "2026-10-10T12:00:00.000Z",
  "data": {
    "domain": "acme.io",
    "previous_status": "registered",
    "current_status": "expiring_soon",
    "expiry_date": "2026-11-09T00:00:00Z",
    "days_until_expiry": 30,
    "checked_at": "2026-10-10T12:00:00.000Z"
  }
}
```

#### Verify signatures

Compute an HMAC-SHA256 of the X-DomScan-Timestamp value, a period and the raw request body using your endpoint secret. Compare the result with X-DomScan-Signature in constant time and reject requests whose timestamp is more than 5 minutes old.

The sample below checks every signature in the header, so it keeps working during a secret rotation.

```javascript
import crypto from 'node:crypto';

// rawBody: the request body exactly as received, before JSON parsing.
function verifyDomScanWebhook(rawBody, headers, secret) {
  const timestamp = headers['x-domscan-timestamp'];
  const header = headers['x-domscan-signature'] || '';
  if (!/^\d{10}$/.test(timestamp || '')) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = Buffer.from('sha256=' + crypto
    .createHmac('sha256', secret)
    .update(timestamp + '.' + rawBody)
    .digest('hex'));
  // One entry normally; two during a secret rotation (new secret first).
  return header.split(',').some((entry) => {
    const candidate = Buffer.from(entry.trim());
    return candidate.length === expected.length &&
      crypto.timingSafeEqual(candidate, expected);
  });
}
```

#### Rotate a signing secret

During the 24 hour grace period after a rotation, X-DomScan-Signature holds two signatures separated by a comma, with the new secret first, as shown below. Accept a request when any entry matches a secret you trust, deploy the new secret, then stop trusting the old one. Rotating again during the grace period retires the older previous secret at once.

```http
X-DomScan-Signature: sha256=<new>,sha256=<previous>
```

#### Retries and automatic pausing

Return any 2xx status within 8 seconds to acknowledge an alert. Redirects, other status codes and timeouts are retried with exponential backoff (1, 4, 16, 64 and 256 minutes), up to 6 attempts in total. After 15 consecutive failed attempts the endpoint is paused; enable it again with PATCH or from Monitor settings.

When an endpoint is paused after repeated failures, the owner of the endpoint receives one email with the endpoint host, the last error and a link to Monitor settings. Fix the endpoint, then enable it again from Monitor settings or with PATCH and enabled set to true. Enabling resets the failure count to zero.

### Watchlist alert subscriptions

Subscribe a watched item to the alerts you want. Each subscription watches one hostname, TLD or brand domain, and you choose which of its events to receive. Events go to your signed webhook endpoints and to your in-app alerts.

| Type | Events | When it fires |
| --- | --- | --- |
| `ssl` | `ssl.expiring`, `ssl.expired`, `ssl.changed` | ssl.expiring when the certificate a hostname serves expires within ssl_expiring_days (30, 14, 7 or 1, default 14), once per certificate and threshold. ssl.expired once per expired certificate. ssl.changed when a new certificate fingerprint is seen on two consecutive checks; the payload says whether the issuer changed. |
| `price` | `price.dropped` | When the registration or renewal price of a TLD, at one registrar or the cheapest one, falls by at least drop_percent from the highest price since the last alert, or reaches target_price or less. Prices come from the daily pricing snapshot, and each snapshot is evaluated once. |
| `dns` | `dns.changed` | When a watched record type (A, AAAA, MX, NS, TXT or CNAME) of any hostname returns the same new answer on two consecutive confirmed lookups. |
| `typosquat` | `typosquat.detected` | When a lookalike of the brand domain is newly registered, confirmed by an authoritative registration lookup. The first full pass over the lookalikes sets the baseline and does not alert. |

Only exact evidence triggers an event. A failed TLS handshake, a DNS lookup that fails or times out, a missing price and a lookup that is blocked or rate limited are unknown: they change nothing and never alert or charge. The first confirmed check of a subscription sets its baseline.

Checks run automatically: certificates every 12 hours, DNS records every hour (with a confirming check 15 minutes after a change is first seen), prices every 2 hours against the daily snapshot, and lookalikes in batches of 30 with a full pass every day. After an unknown check the next attempt comes sooner.

Creating subscriptions and running checks is free. Each alert delivered to a webhook endpoint with a 2xx response costs 2 credits, like every other monitoring alert. Failed deliveries are refunded, and an account can have up to 100 subscriptions, 20 of them for lookalikes.

POST /v1/watchlist/alerts

Create a subscription. type decides which fields apply: domain for ssl, dns and typosquat; tld, registrar, price_type, drop_percent and target_price for price. Fields that do not apply to the type are rejected.

#### Request body

| Parameter | Type | Description |
| --- | --- | --- |
| type | string | ssl, price, dns or typosquat. Required. |
| domain | string | Hostname to watch for ssl and dns, or the brand domain for typosquat. |
| tld | string | TLD to watch for price, for example com. |
| registrar | string | Registrar id for price. Omit it to watch the cheapest registrar. |
| events | string[] | Events to receive. Defaults to every event of the type. |
| ssl_expiring_days | integer | Days before expiry for ssl.expiring: 30, 14, 7 or 1. Default 14. |
| price_type | string | registration (default) or renewal. |
| drop_percent | number | Alert on a drop of at least this percentage, 1 to 95. |
| target_price | number | Alert when the price in USD reaches this amount or less. A price subscription needs drop_percent, target_price or both. |
| record_types | string[] | Record types to watch for dns. Defaults to A, AAAA, MX, NS, TXT and CNAME. |

#### Example request

```bash
curl -X POST "https://domscan.net/v1/watchlist/alerts" \
  -H "X-API-Key: $DOMSCAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "price",
    "tld": "com",
    "registrar": "porkbun",
    "price_type": "renewal",
    "drop_percent": 10,
    "target_price": 9.5
  }'
```

#### Example response

```json
{
  "subscription": {
    "id": "mas_8c1f0e2d3b4a59687766554433221100",
    "type": "price",
    "target": "com",
    "registrar": "porkbun",
    "events": ["price.dropped"],
    "status": "active",
    "price_type": "renewal",
    "drop_percent": 10,
    "target_price": 9.5,
    "currency": "USD",
    "observed": null,
    "last_check_status": null,
    "next_check_at": "2026-10-10T12:00:00.000Z"
  }
}
```

#### Response fields

| Field | Type |
| --- | --- |
| `subscription` | object |
| `subscription.id` | string |
| `subscription.type` | string |
| `subscription.target` | string |
| `subscription.registrar` | string \| null |
| `subscription.events[]` | string[] |
| `subscription.status` | string |
| `subscription.ssl_expiring_days` | integer \| null |
| `subscription.price_type` | string \| null |
| `subscription.drop_percent` | number \| null |
| `subscription.target_price` | number \| null |
| `subscription.currency` | string \| null |
| `subscription.record_types[]` | string[] |
| `subscription.observed` | object \| null |
| `subscription.last_check_at` | string \| null |
| `subscription.last_check_status` | string \| null |
| `subscription.last_error` | string \| null |
| `subscription.next_check_at` | string \| null |
| `subscription.created_at` | string |
| `subscription.updated_at` | string |

GET /v1/watchlist/alerts

List your subscriptions with their latest confirmed evidence and check status, the events each type supports, the check schedule and the alert price.

#### Response fields

| Field | Type |
| --- | --- |
| `subscriptions[]` | object[] |
| `subscriptions[]` | object |
| `subscriptions[].id` | string |
| `subscriptions[].type` | string |
| `subscriptions[].target` | string |
| `subscriptions[].registrar` | string \| null |
| `subscriptions[].events[]` | string[] |
| `subscriptions[].status` | string |
| `subscriptions[].ssl_expiring_days` | integer \| null |
| `subscriptions[].price_type` | string \| null |
| `subscriptions[].drop_percent` | number \| null |
| `subscriptions[].target_price` | number \| null |
| `subscriptions[].currency` | string \| null |
| `subscriptions[].record_types[]` | string[] |
| `subscriptions[].observed` | object \| null |
| `subscriptions[].last_check_at` | string \| null |
| `subscriptions[].last_check_status` | string \| null |
| `subscriptions[].last_error` | string \| null |
| `subscriptions[].next_check_at` | string \| null |
| `subscriptions[].created_at` | string |
| `subscriptions[].updated_at` | string |
| `total` | integer |
| `available_types` | object |
| `available_types.ssl[]` | string[] |
| `available_types.price[]` | string[] |
| `available_types.dns[]` | string[] |
| `available_types.typosquat[]` | string[] |
| `policy` | object |
| `policy.credits_per_delivered_alert` | integer |
| `policy.check_credits` | integer |
| `policy.unknown_checks_charged` | boolean |
| `policy.max_subscriptions` | integer |
| `policy.max_typosquat_subscriptions` | integer |
| `policy.ssl_expiring_days[]` | integer[] |
| `policy.default_ssl_expiring_days` | integer |
| `policy.record_types[]` | string[] |
| `policy.price_types[]` | string[] |
| `policy.check_interval_minutes` | object |
| `policy.confirmation_checks` | object |

#### Example response

```json
{
  "subscriptions": [
    {
      "id": "example-id",
      "type": "ssl",
      "target": "example",
      "registrar": "example",
      "events": [
        "ssl.expiring"
      ],
      "status": "active",
      "ssl_expiring_days": 30,
      "price_type": "registration",
      "drop_percent": 0.5,
      "target_price": 0.5,
      "currency": "USD",
      "record_types": [
        "A"
      ],
      "observed": {},
      "last_check_at": "2026-08-27T12:00:00Z",
      "last_check_status": "baseline",
      "last_error": "example",
      "next_check_at": "2026-08-27T12:00:00Z",
      "created_at": "2026-08-27T12:00:00Z",
      "updated_at": "2026-08-27T12:00:00Z"
    }
  ],
  "total": 1,
  "available_types": {
    "ssl": [
      "ssl.expiring"
    ],
    "price": [
      "price.dropped"
    ],
    "dns": [
      "dns.changed"
    ],
    "typosquat": [
      "typosquat.detected"
    ]
  },
  "policy": {
    "credits_per_delivered_alert": 2,
    "check_credits": 0,
    "unknown_checks_charged": false,
    "max_subscriptions": 1,
    "check_interval_minutes": {},
    "max_typosquat_subscriptions": 1,
    "ssl_expiring_days": [
      1
    ],
    "default_ssl_expiring_days": 1,
    "record_types": [
      "A"
    ],
    "price_types": [
      "registration"
    ],
    "confirmation_checks": {}
  }
}
```

GET /v1/watchlist/alerts/:subscription_id

Get one subscription with its latest confirmed evidence and its 20 most recent events.

#### Response fields

| Field | Type |
| --- | --- |
| `subscription` | object |
| `subscription.id` | string |
| `subscription.type` | string |
| `subscription.target` | string |
| `subscription.registrar` | string \| null |
| `subscription.events[]` | string[] |
| `subscription.status` | string |
| `subscription.ssl_expiring_days` | integer \| null |
| `subscription.price_type` | string \| null |
| `subscription.drop_percent` | number \| null |
| `subscription.target_price` | number \| null |
| `subscription.currency` | string \| null |
| `subscription.record_types[]` | string[] |
| `subscription.observed` | object \| null |
| `subscription.last_check_at` | string \| null |
| `subscription.last_check_status` | string \| null |
| `subscription.last_error` | string \| null |
| `subscription.next_check_at` | string \| null |
| `subscription.created_at` | string |
| `subscription.updated_at` | string |
| `events[]` | object[] |
| `events[]` | object |
| `events[].id` | string |
| `events[].event_type` | string |
| `events[].detected_at` | string |
| `events[].published_at` | string \| null |
| `events[].data` | object |

#### Example response

```json
{
  "subscription": {
    "id": "example-id",
    "type": "ssl",
    "target": "example",
    "registrar": "example",
    "events": [
      "ssl.expiring"
    ],
    "status": "active",
    "ssl_expiring_days": 30,
    "price_type": "registration",
    "drop_percent": 0.5,
    "target_price": 0.5,
    "currency": "USD",
    "record_types": [
      "A"
    ],
    "observed": {},
    "last_check_at": "2026-08-27T12:00:00Z",
    "last_check_status": "baseline",
    "last_error": "example",
    "next_check_at": "2026-08-27T12:00:00Z",
    "created_at": "2026-08-27T12:00:00Z",
    "updated_at": "2026-08-27T12:00:00Z"
  },
  "events": [
    {
      "id": "example-id",
      "event_type": "ssl.expiring",
      "detected_at": "2026-08-27T12:00:00Z",
      "published_at": "2026-08-27T12:00:00Z",
      "data": {}
    }
  ]
}
```

PATCH /v1/watchlist/alerts/:subscription_id

Pause or resume a subscription with enabled, or change its events, ssl_expiring_days, drop_percent, target_price or record_types. A new target price only alerts on a later price movement.

```json
{ "enabled": false }
```

#### Response fields

| Field | Type |
| --- | --- |
| `subscription` | object |
| `subscription.id` | string |
| `subscription.type` | string |
| `subscription.target` | string |
| `subscription.registrar` | string \| null |
| `subscription.events[]` | string[] |
| `subscription.status` | string |
| `subscription.ssl_expiring_days` | integer \| null |
| `subscription.price_type` | string \| null |
| `subscription.drop_percent` | number \| null |
| `subscription.target_price` | number \| null |
| `subscription.currency` | string \| null |
| `subscription.record_types[]` | string[] |
| `subscription.observed` | object \| null |
| `subscription.last_check_at` | string \| null |
| `subscription.last_check_status` | string \| null |
| `subscription.last_error` | string \| null |
| `subscription.next_check_at` | string \| null |
| `subscription.created_at` | string |
| `subscription.updated_at` | string |

#### Example response

```json
{
  "subscription": {
    "id": "example-id",
    "type": "ssl",
    "target": "example",
    "registrar": "example",
    "events": [
      "ssl.expiring"
    ],
    "status": "active",
    "ssl_expiring_days": 30,
    "price_type": "registration",
    "drop_percent": 0.5,
    "target_price": 0.5,
    "currency": "USD",
    "record_types": [
      "A"
    ],
    "observed": {},
    "last_check_at": "2026-08-27T12:00:00Z",
    "last_check_status": "baseline",
    "last_error": "example",
    "next_check_at": "2026-08-27T12:00:00Z",
    "created_at": "2026-08-27T12:00:00Z",
    "updated_at": "2026-08-27T12:00:00Z"
  }
}
```

DELETE /v1/watchlist/alerts/:subscription_id

Delete a subscription. Events it has not sent yet are dropped and never charged.

#### Response fields

| Field | Type |
| --- | --- |
| `id` | string |
| `deleted` | boolean |

#### Example response

```json
{
  "id": "example-id",
  "deleted": true
}
```

#### Example dns.changed event

```json
{
  "id": "evt_2b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e",
  "type": "dns.changed",
  "created": "2026-10-10T12:15:00.000Z",
  "data": {
    "domain": "partner-shop.example",
    "changes": [
      {
        "record_type": "MX",
        "before": ["10 mx1.mail.example"],
        "after": ["10 mx.other-mail.example"],
        "added": ["10 mx.other-mail.example"],
        "removed": ["10 mx1.mail.example"],
        "first_seen_at": "2026-10-10T12:00:00.000Z"
      }
    ],
    "confirmed_checks": 2,
    "confirmed_at": "2026-10-10T12:15:00.000Z",
    "subscription_id": "mas_8c1f0e2d3b4a59687766554433221100"
  }
}
```
