REST API Reference¶
Ferrous DNS exposes a REST API for managing all aspects of the server. The API is served on the same port as the web dashboard (web_port, default 8080).
Base URL¶
| Mode | Base URL |
|---|---|
| Normal | http://<server>:8080/api |
| Pi-hole compat | http://<server>:8080/ferrous/api |
When pihole_compat = true, the Ferrous API moves to /ferrous/api/* and the Pi-hole v6 API occupies /api/*.
Interactive Documentation (OpenAPI / Scalar)¶
Both APIs publish an OpenAPI 3.x specification and ship a built-in Scalar UI for interactive exploration. The endpoints are public — no authentication is required to read the spec or open the UI.
| Mode | OpenAPI spec | Interactive docs |
|---|---|---|
| Normal | GET /api/openapi.json | GET /api/docs |
| Pi-hole compat (native API) | GET /ferrous/api/openapi.json | GET /ferrous/api/docs |
| Pi-hole compat (Pi-hole API) | GET /api/openapi.json | GET /api/docs |
The spec describes every handler, request/response schema, parameter and security scheme (session_cookie + X-Api-Key for the native API, X-FTL-SID for the Pi-hole layer). It can be fed into any OpenAPI-aware tool (Postman, openapi-generator, schemathesis, …) to produce clients or contract tests — see Integrations for examples.
Authentication¶
When authentication is enabled ([auth] section in config), all API endpoints require either a valid session cookie or an API token — except the public auth endpoints listed below.
Session Authentication¶
Authenticate via the login endpoint to receive a session cookie:
POST /api/auth/login
Content-Type: application/json
{
"username": "admin",
"password": "your-password"
}
The server sets a ferrous_session cookie on successful login. The cookie is sent automatically with subsequent requests from the dashboard.
API Token Authentication¶
For programmatic access, include an API token in the X-Api-Key header:
Create and manage tokens via the API Token endpoints below.
Both methods accepted
The auth guard accepts either a valid session cookie or an X-Api-Key header. You do not need both.
Response Format¶
All responses are JSON. Successful mutations return:
Errors return an appropriate HTTP status code with:
Health & System¶
Health Check¶
Returns server health status.
System Info¶
Returns system information: kernel version, load averages, memory usage.
Hostname¶
Returns the server hostname.
Statistics¶
Dashboard¶
Returns a single aggregated payload for the dashboard view: summary counts, the query timeline, top blocked domains, top clients and the query-type breakdown. Use the optional period_hours parameter to change the look-back window (defaults to 24 hours).
Summary Stats¶
Returns aggregated query statistics: total queries, blocked queries, block rate.
Query Rate¶
Returns the current query rate. Supports unit=second or unit=minute.
Query Timeline¶
Returns query volume over time for dashboard graphs.
Top Blocked Domains¶
Top Clients¶
Query Log¶
List Queries¶
Returns recent DNS queries with filtering support.
| Parameter | Type | Description |
|---|---|---|
limit | integer | Max results (default: 100) |
offset | integer | Pagination offset |
protocol | string | Transport the client used: udp, tcp, dot, doh or doq. Case-insensitive; any other value returns 400 |
Each entry carries a protocol field with the same values in lowercase, or null for internally generated queries and for rows logged before the transport was recorded.
Configuration¶
Get Config¶
Returns the full current configuration including server, DNS, blocking, logging, and database settings.
Update Config¶
Partial update — only include the sections you want to change:
Server settings (require restart):
Reload Config¶
Reloads the configuration from the TOML file without restarting the server. DNS, blocking, and cache settings take effect immediately. Server-level settings (ports, pihole_compat) require a full restart.
Get Settings¶
Returns DNS-specific settings (non-FQDN blocking, PTR blocking, local domain).
Update Settings¶
Full replace — unlike POST /api/config (a partial update), this endpoint overwrites the entire DNS settings form. Any field you omit reverts to its default: an omitted sinkhole_ipv4 clears a previously-set sinkhole, an omitted block_mode resets it to null_ip. Send the complete object:
{
"never_forward_non_fqdn": true,
"never_forward_reverse_lookups": true,
"local_domain": "lan",
"local_dns_server": "192.168.1.1:53",
"block_mode": "null_ip",
"block_ttl": 60,
"sinkhole_ipv4": "192.168.1.2",
"sinkhole_ipv6": "fd00::2"
}
sinkhole_ipv4 / sinkhole_ipv6 set a custom block target for null_ip mode (empty string = the null address 0.0.0.0 / ::). A non-empty value that is not a valid address of the matching family is rejected with { "success": false, "error": "Invalid IPv4 sinkhole address: …" } and nothing is saved. See Custom Sinkhole IP.
TLS Certificates¶
Manage the certificate used for the HTTPS web interface.
TLS Status¶
Returns the current certificate status: whether TLS is enabled, whether the cert and key files exist, the certificate subject, expiry (cert_not_after) and whether it is currently valid.
Upload Certificates¶
Uploads a PEM cert and key pair via multipart form fields.
Error codes: 400 Bad Request (missing or invalid files), 401 Unauthorized
Generate Self-Signed¶
Generates a self-signed certificate/key pair. Pass ?force=true to overwrite existing files.
Error codes: 400 Bad Request (files already exist — use ?force=true), 401 Unauthorized
Configuration Backup¶
Export and import the full Ferrous DNS configuration (blocklists, allowlists, groups, clients, custom domains, settings) as a JSON snapshot.
Export Config¶
Returns a backup JSON document as a download (Content-Disposition: attachment, filename ferrous-backup-YYYY-MM-DD.json).
Import Config¶
Restores configuration from a previously exported backup file uploaded as a multipart field. Returns an import summary describing what was applied.
Error codes: 400 Bad Request (invalid backup file), 401 Unauthorized
Auth Endpoints¶
Auth Status¶
Returns whether authentication is enabled and whether a password has been configured. Public — no auth required.
First-Run Setup¶
Sets the admin password on first run (when no password is configured). Public — no auth required.
Warning
This endpoint is only available when password_hash is empty. Once a password is set, it returns 403 Forbidden.
Login¶
Authenticates with username and password. Returns a session cookie (ferrous_session).
| Field | Type | Default | Description |
|---|---|---|---|
username | str | — | Admin username |
password | str | — | Admin password |
remember_me | bool | false | Extend session lifetime to remember_me_days |
Logout¶
Invalidates the current session. Public — no auth required (clears session if present).
Change Password¶
Changes the admin password. Protected — requires valid session or API token.
List Sessions¶
Returns all active sessions. Protected.
Revoke Session¶
Revokes a specific session by ID. Protected.
API Tokens¶
Named API tokens for programmatic access. Tokens are stored as SHA-256 hashes — the full token is only shown once at creation.
List Tokens¶
Returns all tokens. Only the token prefix is shown in the listing.
Create Token¶
Response includes the full token value — save it immediately:
Update Token¶
Update the token name or import a custom key:
Pi-hole migration
Use the key field to import existing API keys from Pi-hole or other tools.
Delete Token¶
User Management¶
List Users¶
Returns all users. Protected.
Create User¶
Delete User¶
Cache¶
Cache Stats¶
Returns cache hit/miss counts, hit rate, and total entries.
Cache Metrics¶
Returns detailed cache metrics: hits, misses, evictions, insertions, optimistic refreshes, lazy deletions, compactions, hit rate.
List Cache Entries¶
Returns the entries currently held in the positive cache, with filtering, ordering, and pagination.
| Parameter | Type | Description |
|---|---|---|
limit | integer | Max results (default: 25, max: 500) |
offset | integer | Pagination offset |
domain | string | Case-insensitive substring match on the cached domain |
type | string | Record type name, e.g. A, AAAA, CNAME |
sort | string | hits, cached_at, expires_at, domain, or type (default: cached_at) |
order | string | asc or desc (default: desc) |
The response is { "data": [...], "total": 0, "records_total": 0, "limit": 25, "offset": 0 }, where total counts the entries matching the filters and records_total counts every entry in the cache. Each item carries domain, type, answers, canonical_name, dnssec_status, ttl, remaining_ttl, cached_at, expires_at, hits, last_access, permanent, and stale. Timestamps are UNIX epoch seconds; remaining_ttl and expires_at are null for permanent entries.
hits counts only lookups served from the shared cache — queries absorbed by the per-thread L1 cache are not included, so hot A/AAAA records report fewer hits than they actually served.
Delete Cache Entry¶
Removes a single cache entry. Returns 204 No Content on success, 404 if the entry is not cached, and 400 if the record type is unknown.
Upstream Health¶
Health Summary¶
Returns health status per upstream server (Healthy / Unhealthy).
Health Detail¶
Returns detailed health information per upstream: pool name, strategy, latency metrics, failure counts.
Clients¶
List Clients¶
Returns all detected clients with IP, MAC, hostname, group, query count, and last seen.
Client Stats¶
Returns per-client query statistics.
Create Manual Client¶
Update Client¶
Delete Client¶
Assign Client to Group¶
Client Subnets¶
Subnets auto-assign clients matching a CIDR range to a group.
List Subnets¶
Create Subnet¶
Delete Subnet¶
Groups¶
List Groups¶
Create Group¶
Get Group¶
Update Group¶
Delete Group¶
Get Group Clients¶
Blocklist Sources¶
List Sources¶
Create Source¶
{
"name": "HaGeZi Pro",
"url": "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/wildcard/pro.txt",
"enabled": true
}
The new source is downloaded and the block index rebuilt as part of the request, so it takes effect on the next query.
Get Source¶
Update Source¶
Delete Source¶
Sync Sources¶
Refreshes the given source. The compiled index is a single snapshot keyed by a global source bitset, so one list cannot be re-downloaded on its own: this rebuilds the index and therefore re-downloads every enabled source. The id records which list the operator asked for and yields 404 when it does not exist.
Returns 202 Accepted as soon as the rebuild starts; it then runs in the background, since refreshing large lists can take minutes.
Error codes: 404 Not Found, 409 Conflict (a sync is already running), 401 Unauthorized
Whitelist Sources¶
List Sources¶
Create Source¶
Get / Update / Delete¶
Managed Domains¶
Individual domains added to the blocklist or allowlist via the dashboard.
List Domains¶
Create Domain¶
Get / Update / Delete¶
Regex Filters¶
List Filters¶
Create Filter¶
Get / Update / Delete¶
Block Filter Stats¶
Returns blocking engine statistics: total domains in blocklist, total in allowlist, filter size.
Blocklist & Allowlist (Compiled)¶
Get Active Blocklist¶
Returns the full compiled blocklist currently in memory.
Get Active Allowlist¶
Returns the full compiled allowlist currently in memory.
Services (1-Click Blocking)¶
Service Catalog¶
Returns all available service categories (built-in + custom).
Returns a specific service definition with its domain list.
Blocked Services¶
Returns services currently blocked for a group.
Block Service¶
Unblock Service¶
Custom Services¶
Define your own blockable service categories.
List / Create¶
{
"name": "My Custom Tracker",
"domains": ["tracker1.example.com", "tracker2.example.com"],
"category": "tracking"
}
Get / Update / Delete¶
Safe Search¶
Get Configs¶
Toggle Safe Search¶
Delete Configs¶
Local DNS Records¶
Static A/AAAA records served directly from cache.
List Records¶
Create Record¶
Update / Delete¶
Schedule Profiles¶
Time-based blocking profiles for parental controls.
List / Create Profiles¶
Get / Update / Delete Profile¶
Manage Time Slots¶
Assign Schedule to Group¶
Pi-hole v6 Compatibility API¶
When pihole_compat = true, the Pi-hole v6 endpoints below are available at /api/* (and the native API moves to /ferrous/api/*). The compatibility layer is not read-only — it implements full CRUD for domains, lists, groups and clients, a blocking toggle, and the Pi-hole action endpoints.
Auth & session
| Method | Endpoint | Description |
|---|---|---|
POST | /api/auth | Pi-hole v6 login (session-based) |
GET | /api/auth | Session status |
DELETE | /api/auth | Logout |
Stats & history
| Method | Endpoint | Description |
|---|---|---|
GET | /api/stats/summary | Dashboard summary stats |
GET | /api/stats/history | Query history timeline (also at /api/history) |
GET | /api/stats/top_blocked | Top blocked domains |
GET | /api/stats/top_clients | Top querying clients |
GET | /api/stats/top_domains | Top allowed domains (?blocked=true for blocked) |
GET | /api/stats/query_types | Query type distribution |
GET | /api/stats/upstreams | Upstream usage |
GET | /api/stats/recent_blocked | Most recently blocked domain |
GET | /api/history/clients | Per-client query totals (last 24 h) |
Several of these are mirrored under /api/stats/database/* for Pi-hole clients.
Queries & search
| Method | Endpoint | Description |
|---|---|---|
GET | /api/queries | Paginated query log (filters: domain, client, status, …) |
GET | /api/queries/suggestions | Filter suggestions |
GET | /api/search/{domain} | Check if a domain would be blocked |
DNS blocking toggle
| Method | Endpoint | Description |
|---|---|---|
GET | /api/dns/blocking | Current blocking status |
POST | /api/dns/blocking | Enable/disable blocking (optional timer) |
Domains (CRUD)
| Method | Endpoint | Description |
|---|---|---|
GET | /api/domains | List all domains |
GET | /api/domains/{type} | List by type (allow/deny) |
GET | /api/domains/{type}/{kind} | List by type and kind (exact/regex) |
POST | /api/domains/{type}/{kind} | Create a domain |
PUT | /api/domains/{type}/{kind}/{domain} | Update a domain |
DELETE | /api/domains/{type}/{kind}/{domain} | Delete a domain |
POST | /api/domains:batchDelete | Batch delete |
Lists / adlists (CRUD)
| Method | Endpoint | Description |
|---|---|---|
GET | /api/lists | List adlists |
POST | /api/lists | Create an adlist |
GET | /api/lists/{id} | Get an adlist |
PUT | /api/lists/{id} | Update an adlist |
DELETE | /api/lists/{id} | Delete an adlist |
POST | /api/lists:batchDelete | Batch delete |
Groups (CRUD)
| Method | Endpoint | Description |
|---|---|---|
GET | /api/groups | List groups |
POST | /api/groups | Create a group |
GET | /api/groups/{name} | Get a group |
PUT | /api/groups/{name} | Update a group |
DELETE | /api/groups/{name} | Delete a group |
POST | /api/groups:batchDelete | Batch delete |
Clients (CRUD)
| Method | Endpoint | Description |
|---|---|---|
GET | /api/clients | List clients (limit, offset) |
POST | /api/clients | Create a client |
GET | /api/clients/_suggestions | IP/hostname suggestions |
PUT | /api/clients/{client} | Update a client (by IP) |
DELETE | /api/clients/{client} | Delete a client (by IP) |
POST | /api/clients:batchDelete | Batch delete |
Info
| Method | Endpoint | Description |
|---|---|---|
GET | /api/info/version | Version info |
GET | /api/info/ftl | FTL daemon info |
GET | /api/info/system | Host system info |
GET | /api/info/host | Host hostname |
GET | /api/info/database | Query database info |
Actions
| Method | Endpoint | Description |
|---|---|---|
POST | /api/action/gravity | Trigger a blocklist (gravity) reload |
POST | /api/action/restartdns | Reload configuration in-memory |
POST | /api/action/flush/logs | Clean up old query logs |
See Pi-hole Compatibility for details.