Domain Metrics API /v1
External REST API for creating scrape jobs and receiving results via callbacks. Authentication uses Bearer API keys; every endpoint is owner-scoped to the calling key.
Authentication
Every /v1 request requires an API key with at least one scope. Keys are scoped to a single tenant — you only see your own jobs and results.
Authorization: Bearer dm_jobs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Equivalent header: X-API-Key: dm_jobs_...
Scopes
| Scope | Allows |
|---|---|
jobs:create | POST /v1/jobs · DELETE /v1/jobs/<id> |
jobs:read | GET /v1/jobs · GET /v1/jobs/<id> · GET /v1/jobs/<id>/results |
keys:admin | POST /v1/keys · DELETE /v1/keys/<id> |
keys:read | GET /v1/keys (admin sees all; non-admin sees own row) |
Endpoints
Create a job
POST /v1/jobs
Authorization: Bearer dm_jobs_...
Content-Type: application/json
{
"domains": ["bnt.bg", "btv.bg", "nova.bg"],
"job_types": ["seokicks", "moz"],
"webhook_url": "https://your-app.example.com/webhooks/domain-metrics"
}
Response (201):
{
"job_id": "f1167536-26a6-4f5e-88f1-e10c6ec01fab",
"status": "queued",
"created_at": "2026-04-25T12:00:00Z",
"webhook_secret": "wsec_xxxxxxxx..."
}
Available job_types: seokicks, moz, ahrefs_backlinks, ahrefs_traffic, wayback, ping.
List jobs
GET /v1/jobs?limit=50&cursor=<last_seen_id>
Returns: {"jobs": [...], "next_cursor": "..."}. Keyset pagination on (created_at, id) DESC.
Job status
GET /v1/jobs/<id>
Job results
GET /v1/jobs/<id>/results
Returns one row per (domain, source). Typed columns from results_core + the JSONB raw payload from each source.
Cancel a job
DELETE /v1/jobs/<id>
Soft-cancel. Currently transitions queued/running jobs to paused; hard-cancel/cleanup planned for a follow-up.
API keys (admin)
POST /v1/keys
{ "name": "tenant-acme", "scopes": ["jobs:create", "jobs:read"] }
→ { "id": "...", "token": "dm_jobs_xxx" }
GET /v1/keys
DELETE /v1/keys/<id>
Webhooks
If you set webhook_url when creating a job, we POST a JSON payload to that URL when the job hits a terminal status (completed or failed). Requests are signed with HMAC-SHA256 using the webhook_secret returned at creation.
Payload
POST <your_webhook_url>
Content-Type: application/json
X-DomainMetrics-Signature: sha256=<hex_hmac_of_body>
{
"job_id": "f1167536-26a6-4f5e-88f1-e10c6ec01fab",
"status": "completed",
"completed_at": "2026-04-25T12:00:42Z",
"results_url": "https://metrics.example.com/v1/jobs/f116.../results"
}
Verifying the signature (Python)
import hmac, hashlib
def verify(body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, header)
Verifying the signature (Node)
const crypto = require("crypto");
function verify(body, header, secret) {
const expected = "sha256=" +
crypto.createHmac("sha256", secret).update(body).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}
Retry policy
- 5xx / network error: retried up to 3 times with backoff 5s → 30s → 5min.
- 4xx response: dropped, no retry. Fix your endpoint.
- Every attempt is recorded in our internal
webhook_deliverieslog; ask if you need delivery history exposed via the API.
Source matrix (per scrape job)
| Source | Speed | Bulk | Returns |
|---|---|---|---|
seokicks | ~1s/domain | — | linkpop, domainpop, ip_pop, classc_pop, 50 referring rows |
moz | ~3s/domain | 10/batch | DA, PA, spam score, linking domains, inbound links |
ahrefs_backlinks | ~30s/domain | — | DR, backlinks, ref domains, dofollow % |
ahrefs_traffic | ~34s/domain | — | organic traffic, traffic value, top keywords/pages |
wayback | ~10s/domain | — | archived snapshot count |
ping | sub-second | — | reachability + status code |