Account & API
API keys and REST API v1
Available on the Pro, Agency and Provider plansUpdated September 2026
Create a preview for every pull request, every staging deploy or every site in a hosting migration, straight from a script or your CI. The REST API manages preview links, runs QA reports and checks DNS propagation with the same rules and plan limits as the dashboard. Every endpoint, field and error code is in the REST API v1 reference.
On this page
Create an API key
Open API Keys in the sidebar
Owners and admins of a workspace on a plan with API access can create keys.
Name the key and choose its access, expiry and IPs
Use a name that says where it is used, such as “Production” or “CI/CD”. Pick Read & write or Read only, when the key expires (30 days, 90 days, 1 year or never) and, optionally, the IP addresses it may be used from.
Copy the key now
Keys start with
dpk_and are shown only once. Store the key in your CI’s secret store, never in code.
The list shows each key’s name, prefix, access, IP limit, expiry and its last use: when, from which network (the last part of the address is hidden) and with which client. Revoke a key the moment you no longer need it or suspect it leaked.
Access: read only or read and write
| Access | What the key can do |
|---|---|
| Read & write | Everything: create, change and delete links, and start QA reports. |
| Read only | Only requests that read (GET), such as listing links, reading QA reports or checking DNS. Anything that changes data answers 403 with API_KEY_SCOPE. |
Give dashboards, reports and monitoring a read-only key. Keys created before access levels existed have read and write access.
Expiry
A key can expire 30 days, 90 days or 1 year after it is created, or never. The person who created it gets a reminder 7 days and 1 day before (by email and in the notification bell; turn either off in your notification settings), and another when it has expired. An expired key answers 401 with API_KEY_EXPIRED: create a new key, update your secrets, then revoke the old one.
Allowed IP addresses
Limit a key to your servers or CI runners by listing up to 20 IPv4 or IPv6 addresses or CIDR ranges, such as 203.0.113.10 or 198.51.100.0/24. A request from any other address answers 403 with API_KEY_IP_NOT_ALLOWED, even with the right key. Leave the list empty to allow any address.
Your personal IP allowlist (Settings → Security) and your team’s Network access still apply on top of the key’s own list.
Rotate a key
Rotate on the API Keys page gives a key a new secret and keeps its name, prefix, access, expiry and IP list. The new key is shown once, and the old secret stops working immediately, so update your secrets right away. Rotating is recorded in the team’s activity log.
Authenticate
Send the key as a bearer token on every request. All endpoints live under https://api.dnspeek.link/api/v1.
curl https://api.dnspeek.link/api/v1/links \
-H "Authorization: Bearer $DNSPEEK_API_KEY"Endpoints
| Request | Access | What it does |
|---|---|---|
GET /api/v1/links | read | Links of the workspace, newest first, one page at a time: shared links plus the key creator’s private links. |
POST /api/v1/links | write | Creates a link and returns it. |
GET /api/v1/links/:alias | read | One link with its preview URL, status and settings. |
PATCH /api/v1/links/:alias | write | Turns the preview on or off, sets or removes the password and expiry, or moves it to another server IP. |
DELETE /api/v1/links/:alias | write | Deletes a link (200 with an empty body). |
POST /api/v1/links/:alias/qa-reports | write | Starts a QA report of the new server (uses the monthly quota). |
GET /api/v1/links/:alias/qa-reports | read | QA reports of a link, newest first. |
GET /api/v1/qa-reports/:id | read | Status, score, verdict and finding counts of a QA report. |
GET /api/v1/dns/propagation?domain=&expected_ip= | read | Whether public resolvers already return the new IP. |
The machine-readable spec is at https://api.dnspeek.link/api/v1/openapi.json (OpenAPI 3), and a ready-made Postman collection at https://api.dnspeek.link/api/v1/postman.json.
Create a link
Fields for creating a link (JSON body):
| Field | Rule |
|---|---|
targetDomain | Required. The site’s domain, up to 255 characters. |
targetIp | Required. Public IP address of the new server. |
alias | Optional. 3–50 lowercase letters, digits or dashes; generated when omitted. 409 if taken. |
httpPort | Optional. 1–65535, default 80. |
httpsPort | Optional. 1–65535, default 443. |
sslMode | Optional. auto (default), skip or verify. |
password | Optional. Up to 128 characters. The response only says hasPassword. |
expiresAt | Optional. ISO 8601 date-time. Plan rules and team policies apply. |
spaceId | Optional. ID of a space in the key’s workspace. |
visibility | Optional. workspace (default) or private: only the key’s creator then sees the link in the dashboard and API. The preview URL works either way. |
curl -X POST https://api.dnspeek.link/api/v1/links \
-H "Authorization: Bearer $DNSPEEK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: deploy-2026-09-28-01" \
-d '{
"targetDomain": "shop.example.com",
"targetIp": "203.0.113.10",
"alias": "shop-preview",
"password": "client-only",
"expiresAt": "2026-12-31T00:00:00Z"
}'{
"id": "0b6f3c1e-…",
"alias": "shop-preview",
"status": "active",
"previewUrl": "https://shop-preview.dnspeek-preview.link/",
"dashboardUrl": "https://dnspeek.link/links/0b6f3c1e-…",
"targetDomain": "shop.example.com",
"targetIp": "203.0.113.10",
"sslMode": "auto",
"hasPassword": true,
"disabled": false,
"expiresAt": "2026-12-31T00:00:00.000Z",
"visibility": "workspace",
"createdAt": "2026-09-28T10:00:00.000Z",
"targetHints": []
}status is active, disabled (turned off by your team), blocked (disabled by DNSPeek, see blockedReason) or expired. Unknown fields are rejected.
Change a link
Send only the fields you want to change: enabled, password (null removes it), expiresAt (null removes it), targetIp, httpPort, httpsPort, sslMode or visibility. A new server IP re-runs the server check. The same checks as in the dashboard apply: public IPs only, the platform blocklist, team policies and plan limits. Only the link’s creator can change visibility (403 otherwise). Other members’ private links answer 404 LINK_NOT_FOUND, as if they did not exist.
curl -X PATCH https://api.dnspeek.link/api/v1/links/shop-preview \
-H "Authorization: Bearer $DNSPEEK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "targetIp": "203.0.113.20", "enabled": true }'curl -X DELETE https://api.dnspeek.link/api/v1/links/shop-preview \
-H "Authorization: Bearer $DNSPEEK_API_KEY"Run a QA report
A QA report crawls the site on the new server and lists broken pages, redirects, mixed content and other launch problems (see QA report). Starting one uses a QA report of your plan’s monthly quota; when it is used up the API answers 402 with QA_QUOTA_EXCEEDED.
curl -X POST https://api.dnspeek.link/api/v1/links/shop-preview/qa-reports \
-H "Authorization: Bearer $DNSPEEK_API_KEY"Send run settings in the body to change the crawl; fields you leave out use the link’s default run settings:
curl -X POST https://api.dnspeek.link/api/v1/links/shop-preview/qa-reports -H "Authorization: Bearer $DNSPEEK_API_KEY" -H "Content-Type: application/json" -d '{"maxPages": 50, "viewports": "both", "excludePaths": ["/cart/**"], "stagingAuth": {"type": "header", "name": "X-Staging-Token", "value": "'"$STAGING_TOKEN"'"}}'Settings beyond your plan (more pages than it allows, "crawlSpeed": "fast" without the feature) answer 402 with PLAN_LIMIT; invalid patterns or header names answer 400 with VALIDATION_FAILED. Neither uses the quota. Staging credentials are stored encrypted, sent only to the link’s domain on the new server and never returned; the report’s settings field shows the settings used without them.
Poll GET /api/v1/qa-reports/:id every few seconds until status is completed or failed (a run usually takes 1–5 minutes). A completed report has a score (0–100), finding counts and a verdict: not_ready when there is any critical finding, ready_with_warnings when there are warnings, otherwise ready. reportUrl opens the full report in the dashboard; shareUrl is the read-only link you can send to a client.
Check DNS propagation
curl "https://api.dnspeek.link/api/v1/dns/propagation?domain=shop.example.com&expected_ip=203.0.113.20" \
-H "Authorization: Bearer $DNSPEEK_API_KEY"The answer lists each public resolver and whether it already returns the new IP, the share that does (percent), and propagated once at least 90% agree. Results are cached for 60 seconds, and each key can make 10 checks a minute. See DNS propagation for what the numbers mean.
Pagination
Lists return { items, total, limit, nextCursor }, newest first. Ask for up to 100 items with ?limit=, then pass nextCursor back as ?cursor= for the next page; it is null on the last page.
Safe retries
Add an Idempotency-Key header (any unique value, such as a UUID or your deploy id) to POST requests. If a request times out and you send it again with the same key and body within 24 hours, you get the first answer back (with Idempotency-Replayed: true) instead of a second link or a second QA report. Reusing a key for a different request answers 422 with IDEMPOTENCY_KEY_REUSED. Staging secrets (a password or header / cookie value) are not part of this comparison and are never stored with the key: a retry that changes only the secret gets the first answer back.
GitHub Action
The DNSPeek Preview action creates or updates a preview for every pull request, can run a QA report and fail the build on a bad verdict, and comments the preview link and results on the pull request.
- uses: dnspeek/preview-action@v1
with:
api-key: ${{ secrets.DNSPEEK_API_KEY }}
domain: shop.example.com
target-ip: 203.0.113.10
alias: shop-pr-${{ github.event.pull_request.number }}
run-qa: true
fail-on-verdict: not_ready
github-token: ${{ secrets.GITHUB_TOKEN }}It outputs preview-url, qa-report-url, qa-score and verdict for later steps.
Errors and limits
Every error has the same shape, so your code can branch on error.code:
{ "error": { "code": "LINK_NOT_FOUND", "message": "No link with alias \"shop-preview\" in this workspace" } }| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_FAILED | An invalid or unknown field; error.details.errors lists the problems. |
| 400 | BAD_REQUEST or POLICY_… | A private IP, a blocked target, or a team policy (the message names the rule). |
| 401 | UNAUTHORIZED | Missing or invalid key (a rotated key’s old secret too), or its creator is no longer a member of the workspace. |
| 401 | API_KEY_EXPIRED | The key has expired. |
| 402 | PLAN_REQUIRED | The workspace plan does not include API access. |
| 402 | QA_QUOTA_EXCEEDED | This month’s QA reports are used up. |
| 403 | API_KEY_SCOPE | The key is read only and the request would change data. |
| 403 | API_KEY_IP_NOT_ALLOWED | The request came from an address outside the key’s allowed IPs. |
| 403 | IP_NOT_ALLOWED | Your personal IP allowlist or your team’s Network access refused the address. |
| 404 | LINK_NOT_FOUND, QA_REPORT_NOT_FOUND | Nothing with that alias or id in the key’s workspace. |
| 409 | CONFLICT | The alias is already taken. |
| 423 | WORKSPACE_SUSPENDED | The workspace is suspended. |
| 429 | RATE_LIMITED | Too many requests for this key and endpoint (the limit comes with your plan; DNS checks: 10 a minute). Wait for Retry-After seconds. |
The rate limit comes with your plan: requests per minute that each API key may send to one endpoint. DNS propagation checks stay at 10 per minute on every plan.
| Plan | Requests per minute per key |
|---|---|
| Pro | 60 |
| Agency | 300 |
| Provider | 1,000 |
Each response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, so a script can slow down before it hits the limit. The 429 message names your limit and plan, and says which plan allows more when there is one.
Need events pushed to you instead of polling? Use a signed webhook channel. Creating many links without code? Try the CSV import.