API Reference

Meridian exposes a bearer-token authenticated REST API at /api/v1/. Every UI action in the portal is a thin wrapper over this API — anything you can do with a browser session, you can automate with an API token. The full machine-readable spec is served at /openapi.json (Swagger/OpenAPI 3.1), and an interactive browser is available at /docs.

1. Minting an API token

In the portal: Settings → API tokens → Create token. The plaintext token is shown once; store it immediately. Its SHA-256 hash is persisted server-side — Meridian cannot recover the original. Tokens carry:

2. Authenticating a request

Send the token in the Authorization header with a Bearer prefix. Meridian recognises tokens by the mrd_ prefix; anything else on that header is treated as a session ID (legacy).

curl -sS https://meridian.example.com/api/v1/monitors/ \
  -H "Authorization: Bearer mrd_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

For POST/PATCH/PUT/DELETE on routes that normally require a CSRF header, API-token requests are exempt — the bearer header itself proves intent. Add Content-Type: application/json for JSON bodies.

3. Permissions and scopes

Every route is guarded by a named permission (e.g. dns.sandbox, cert.request, admin.devices.manage). When a token is used, Meridian checks BOTH the user's current permissions AND the token's stored scopes — the required permission must appear in both. Rotate or revoke a token under Settings → API tokens if its scopes drift out of date.

4. Common endpoints

DNS diagnostics

# Dig against an explicit resolver
curl -sS -X POST /api/v1/dns/dig \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"target":"example.com","record_type":"A","resolver":"1.1.1.1"}'

# Propagation across 16 public resolvers
curl -sS -X POST /api/v1/dns/propagation \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"target":"example.com","record_type":"A"}'

# Hop-trace recursion through a named resolver group
curl -sS -X POST /api/v1/dns/trace \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"target":"example.com","record_type":"A","group_tag":"corp"}'

Network tools

POST /api/v1/network/ping               {"target":"1.1.1.1","count":4}
POST /api/v1/network/traceroute         {"target":"1.1.1.1"}
POST /api/v1/network/http-test          {"url":"https://example.com"}
POST /api/v1/network/port-scan          {"target":"1.1.1.1","ports":"22,80,443"}
POST /api/v1/network/ip-geolocate       {"ip":"8.8.8.8"}
POST /api/v1/network/header-audit       {"url":"https://example.com"}
GET  /api/v1/network/interfaces

Threat intel (vulnerability + reputation)

POST /api/v1/network/cve-lookup         {"cve":"CVE-2021-44228"}
POST /api/v1/network/kev-lookup         {"cve":"CVE-2021-44228"}
POST /api/v1/network/kev-search         {"query":"log4j"}
POST /api/v1/network/epss-lookup        {"cve":"CVE-2021-44228"}
POST /api/v1/network/circl-lookup       {"cve":"CVE-2021-44228"}
POST /api/v1/network/ip-reputation      {"ip":"203.0.113.42"}
POST /api/v1/network/dshield-lookup     {"ip":"203.0.113.42"}
# API-key required:
POST /api/v1/network/abuseipdb-lookup   {"ip":"203.0.113.42"}
POST /api/v1/network/greynoise-lookup   {"ip":"203.0.113.42"}
POST /api/v1/network/virustotal-lookup  {"target":"example.com"}
POST /api/v1/network/urlscan-search     {"query":"domain:example.com"}
POST /api/v1/network/shodan-lookup      {"ip":"203.0.113.42"}
POST /api/v1/network/censys-lookup      {"ip":"203.0.113.42"}

Certificates

GET    /api/v1/certs/                   # list all (portal + monitored)
POST   /api/v1/certs/watchlist          {"host":"example.com","port":443,
                                         "notify_channels":["uuid…"],
                                         "renew_before_days":30}
POST   /api/v1/certs/{id}/refresh       # re-fetch + threshold + fp-change
PATCH  /api/v1/certs/{id}               {"notify_channels":[…],
                                         "renew_before_days":14,
                                         "auto_renew":true}
POST   /api/v1/certs/upload             {"leaf_pem":"…","chain_pem":"…",
                                         "cert_type":"internal"}
POST   /api/v1/certs/csr                {"subject_cn":"…","sans":[],
                                         "key_type":"ecdsa_p256"}
DELETE /api/v1/certs/{id}

Monitors

GET    /api/v1/monitors/                # list
POST   /api/v1/monitors/                {"name":"…","kind":"https",
                                         "target":"api.example.com",
                                         "interval_seconds":300,
                                         "timeout_seconds":10}
PATCH  /api/v1/monitors/{id}            # edit any field
POST   /api/v1/monitors/{id}/toggle     # flip enabled
DELETE /api/v1/monitors/{id}
GET    /api/v1/monitors/{id}/samples    # recent samples
GET    /api/v1/monitors/{id}/incidents  # open + closed incidents

Directory (LDAP / AD / Entra)

POST /api/v1/directory/user/search      {"query":"jsmith","limit":25}
POST /api/v1/directory/group/search     {"query":"Domain Admins","limit":25}
POST /api/v1/directory/integrations/{id}/test

Wizards

POST /api/v1/wizards/run                {"wizard_key":"ssl.deep_inspect",
                                         "target":"example.com"}

Returns: wizard_key · target · outcome · steps[] · suggestions[]. Each step has name · outcome · message · detail.

Runbooks

GET    /api/v1/runbooks                 # list
GET    /api/v1/runbooks/{id}            # detail + steps
POST   /api/v1/runbooks                 {"name":"…","steps":[…]}
POST   /api/v1/runbooks/{id}/run        # execute all steps
GET    /api/v1/runbooks/{id}/runs       # run history
GET    /api/v1/runbooks/{id}/runs/{rid} # full step_results

Admin — integrations

# Directory
GET    /api/v1/admin/integrations/directory
POST   /api/v1/admin/integrations/directory
PATCH  /api/v1/admin/integrations/directory/{id}
DELETE /api/v1/admin/integrations/directory/{id}

# Threat Intel — keys + source toggles
GET    /api/v1/admin/integrations/threat-intel
POST   /api/v1/admin/integrations/threat-intel
PATCH  /api/v1/admin/integrations/threat-intel/{id}
DELETE /api/v1/admin/integrations/threat-intel/{id}
GET    /api/v1/admin/integrations/threat-intel-sources
PATCH  /api/v1/admin/integrations/threat-intel-sources/{source_key}

Admin — network devices

GET    /api/v1/admin/devices
POST   /api/v1/admin/devices
PATCH  /api/v1/admin/devices/{id}
DELETE /api/v1/admin/devices/{id}
POST   /api/v1/admin/devices/{id}/backup      # trigger on-demand backup
GET    /api/v1/admin/devices/{id}/snapshots
GET    /api/v1/admin/devices/{id}/snapshots/{sid}
GET    /api/v1/admin/devices/{id}/snapshots/{a}/diff/{b}

5. Rate limits

Each token has a per-minute cap (default 120). Exceeding it returns 429 Too Many Requests. The UI session path is unlimited.

6. Error envelope

Errors follow the FastAPI default shape:

HTTP/1.1 403 Forbidden
Content-Type: application/json

{"detail":"API token is missing required scope(s): dns.sandbox"}

Validation failures from Pydantic surface 422 Unprocessable Entity with a detail array of field errors. Upstream-provider failures on Threat Intel routes surface 502 Bad Gateway. A missing Threat Intel key surfaces 412 Precondition Failed.

7. Revoking and rotating

Under Settings → API tokens, each row has Revoke (sets revoked_at; the token no longer authenticates) and Delete (removes the row; equivalent for auth purposes but cleans the list). Minting a new token with the same name as a revoked one is allowed.

8. Interactive browser

The raw OpenAPI 3.1 document is served at /openapi.json — feed it into Postman, Bruno, or an API-client generator. On airgapped deployments (installer flag --airgapped) an interactive Swagger UI is also mounted at /api-docs.