Skip to content

API Reference

The Dreadnought backend exposes a REST API that you can call directly for automation, scripting, or building integrations. The same API is used by the web dashboard.

Base URL: http://localhost:8081 (or your configured API domain)


Authentication

Session Cookies (Web UI)

The web dashboard uses HTTP-only session cookies. After logging in via POST /api/auth/login, the cookie is set automatically.

API Usage from Scripts

For scripting, log in once to get the cookie, then pass it on subsequent requests:

# Step 1: Log in and save the cookie
curl -X POST http://localhost:8081/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "password": "yourpassword"}' \
  -c /tmp/dreadnought-cookies.txt

# Step 2: Use the cookie for authenticated requests
curl http://localhost:8081/api/domains \
  -b /tmp/dreadnought-cookies.txt

Cookies expire after 30 minutes. Re-authenticate if you get a 401 Unauthorized.


Endpoints

Health

GET /health

Returns the API health status. Does not require authentication.

Response:

{"status": "ok"}

Authentication

POST /api/auth/login

Log in and receive a session cookie.

Request body:

{
  "email": "you@example.com",
  "password": "yourpassword"
}

Response (200):

{
  "message": "Login successful"
}

The session cookie is set automatically in the response headers.

Response (401):

{
  "detail": "Invalid credentials"
}

POST /api/auth/logout

Invalidate the current session.

Response (200):

{"message": "Logged out"}

GET /api/auth/me

Get the currently authenticated user.

Response (200):

{
  "id": 1,
  "email": "you@example.com",
  "created_at": "2024-01-15T10:30:00"
}

Domains

GET /api/domains

List all registered domains.

Response (200):

[
  {
    "id": 1,
    "name": "example.com",
    "zone_id": "a1b2c3d4e5f6...",
    "created_at": "2024-01-15T10:30:00",
    "updated_at": "2024-01-15T10:30:00"
  }
]

POST /api/domains

Register a new domain.

Request body:

{
  "name": "example.com",
  "zone_id": "a1b2c3d4e5f6..."
}

Response (200):

{
  "id": 1,
  "name": "example.com",
  "zone_id": "a1b2c3d4e5f6...",
  "created_at": "2024-01-15T10:30:00",
  "updated_at": "2024-01-15T10:30:00"
}

Validation errors: - Domain name must be a valid domain format - Zone ID must be a valid 32-character hex string - Domain name must be unique


PUT /api/domains/{domain_id}

Update an existing domain.

Request body: Same as POST /api/domains

Response: Updated domain object


DELETE /api/domains/{domain_id}

Delete a domain. Fails if any records are still associated with it.

Response (200):

{"message": "Domain deleted"}

Response (400):

{"detail": "Cannot delete domain with existing records"}

Records

GET /api/records

List tracked DNS records. Supports optional query filters.

Query parameters:

Parameter Type Description
domain string Filter by domain name (e.g. example.com)
fqdn string Filter by fully qualified domain name
record_type string Filter by type: A or AAAA

Response (200):

[
  {
    "id": 1,
    "domain_id": 1,
    "domain": "example.com",
    "host": "vpn",
    "fqdn": "vpn.example.com",
    "type": "A",
    "proxied": false,
    "ttl": 300,
    "created_at": "2024-01-15T10:30:00",
    "updated_at": "2024-01-15T10:30:00"
  }
]

POST /api/records

Create a new tracked record. Syncs to Cloudflare immediately.

Request body:

{
  "domain_id": 1,
  "host": "vpn",
  "type": "A",
  "proxied": false,
  "ttl": 300
}

Fields:

Field Required Type Description
domain_id Yes integer ID of the registered domain
host Yes string Subdomain prefix or @ for root
type Yes string A or AAAA
proxied Yes boolean Enable Cloudflare proxy
ttl Yes integer 1–86400 (1 = Auto)

Response (200): Created record object

Possible errors: - 400 — CNAME record exists at that name (conflict) - 400 — Validation error (invalid host, TTL out of range, etc.)


PUT /api/records/{record_id}

Update an existing record. Syncs to Cloudflare immediately.

Request body: Same as POST /api/records

Response (200): Updated record object


DELETE /api/records/{record_id}

Delete a tracked record.

Query parameters:

Parameter Type Default Description
delete_from_cloudflare boolean false If true, also deletes the record from Cloudflare

Example — delete from Dreadnought only:

curl -X DELETE http://localhost:8081/api/records/1 \
  -b /tmp/dreadnought-cookies.txt

Example — delete from Dreadnought and Cloudflare:

curl -X DELETE "http://localhost:8081/api/records/1?delete_from_cloudflare=true" \
  -b /tmp/dreadnought-cookies.txt

Response (200):

{"message": "Record deleted"}

Sync

POST /api/sync

Trigger an immediate sync of all records against the current IP.

Response (200):

{
  "status": "success",
  "records_synced": 5,
  "records_updated": 2,
  "ip": "203.0.113.99"
}

GET /api/status

Get the last known IP and sync status.

Response (200):

{
  "last_seen_ipv4": "203.0.113.99",
  "last_seen_ipv6": null,
  "last_sync_at": "2024-01-15T10:30:00",
  "last_sync_result": "5 records checked, 0 updated"
}

Import

POST /api/import

Bulk import records from a JSON array.

Request body:

{
  "records": [
    {
      "domain": "example.com",
      "host": "vpn",
      "ip_version": 4,
      "ttl": 300,
      "proxied": false
    }
  ],
  "dry_run": true
}

Fields:

Field Required Type Default Description
records Yes array Array of record objects
dry_run No boolean false If true, validate only; don't create records

Record object fields:

Field Required Type Description
domain Yes string Domain name (must be registered)
host Yes string Subdomain prefix or @
ip_version Yes integer 4 or 6
ttl Yes integer 1–86400
proxied Yes boolean

Response (200):

{
  "dry_run": true,
  "results": [
    {
      "host": "vpn",
      "domain": "example.com",
      "fqdn": "vpn.example.com",
      "status": "would_create"
    }
  ],
  "summary": {
    "total": 1,
    "would_create": 1,
    "would_skip": 0,
    "errors": 0
  }
}

Status values: created, skipped, would_create, would_skip, error


Audit Log

GET /api/audit

Retrieve audit log entries, most recent first.

Query parameters:

Parameter Type Default Description
limit integer 50 Number of entries to return
offset integer 0 Pagination offset

Response (200):

[
  {
    "id": 1,
    "ts": "2024-01-15T10:30:00",
    "action": "bulk_sync",
    "details": {
      "records_checked": 5,
      "records_updated": 2,
      "old_ip": "203.0.113.5",
      "new_ip": "203.0.113.99",
      "duration_ms": 1250
    }
  }
]

Settings

GET /api/settings/{key}

Get a setting value.

Valid keys: - last_seen_ipv4 - last_seen_ipv6 - last_sync_at - last_sync_result - ipv6_enabled - poll_interval_seconds - discord_webhook_url

Response (200):

{
  "key": "poll_interval_seconds",
  "value": "300"
}

PUT /api/settings/{key}

Update a setting value.

Request body:

{
  "value": "600"
}

Response (200):

{
  "key": "poll_interval_seconds",
  "value": "600"
}

Example: Full Automation Script

A bash script that logs in and triggers a sync:

#!/bin/bash

API_URL="http://localhost:8081"
EMAIL="you@example.com"
PASSWORD="yourpassword"
COOKIE_JAR="/tmp/dreadnought-session.txt"

# Login
curl -s -X POST "${API_URL}/api/auth/login" \
  -H "Content-Type: application/json" \
  -d "{\"email\": \"${EMAIL}\", \"password\": \"${PASSWORD}\"}" \
  -c "${COOKIE_JAR}" > /dev/null

# Trigger sync
RESULT=$(curl -s -X POST "${API_URL}/api/sync" \
  -b "${COOKIE_JAR}")

echo "Sync result: ${RESULT}"

HTTP Status Codes

Code Meaning
200 Success
400 Bad request (validation error, constraint violation)
401 Unauthorized (not logged in, or session expired)
404 Resource not found
422 Unprocessable entity (malformed request body)
500 Internal server error (check API logs)