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) |