API documentation
Everything the panel does, you can do over HTTPS. One base URL, bearer tokens, JSON in and out.
Base URL https://api.uk.app/v1
Auth Authorization: Bearer <token>
Spec https://api.uk.app/v1/openapi.json
DDNS https://ddns.uk.app/nic/update
MCP https://mcp.uk.app (coming)
Overview
A uk.app name is a third-level name such as petersblog.uk.app. It lives in a zone served by our own authoritative DNS (PowerDNS on two independent networks, DNSSEC-signed). You own every record beneath it: www, api, *, _acme-challenge, anything. Changes publish within seconds; resolver caches follow the TTL you set (default 300 s).
A name goes through these states:
| Status | Meaning |
|---|---|
pending | Reserved for 48 hours, not yet paid. Not in DNS. |
parked | Paid, no records of your own yet — shows the parking page. |
active | Paid and pointing somewhere. |
grace | Renewal failed; keeps working for 30 days. |
hold | Stopped resolving; restorable by paying. |
suspended | Taken down for abuse. Serves a suspension page. |
Authentication
Create tokens at my.uk.app/tokens. Each token carries scopes; requests without the required scope get 403.
| Scope | Allows |
|---|---|
read | List names and records, verify, WHOIS, account info. |
write:records | Add, change, delete records; apply presets. This is all an ACME client or DDNS updater needs. |
write:names | Register and release names. |
Tokens are shown once. Revoking a token takes effect immediately. Keep tokens out of git; for CI use a token with write:records only.
Conventions & errors
- Names can be passed as
petersblogorpetersblog.uk.app; both work everywhere. - Request bodies: JSON (
Content-Type: application/json) or form-encoded. Responses are JSON unless noted. - Validation problems return
422with{"message": "...", "errors": {"field": ["..."]}}. - Rate limit: 600 requests per minute per token. Headers
X-RateLimit-RemainingandRetry-Aftertell you where you stand. - Timestamps are ISO 8601 in UTC.
| Code | When |
|---|---|
| 401 | Missing or invalid token. |
| 403 | Token lacks the scope. |
| 404 | Name or record not found, or not yours. |
| 422 | Invalid input, name unavailable, limit reached, name not active yet. |
| 429 | Rate limited. |
Quick start
export TOKEN=ukapp_...
# 1. is it free?
curl "https://api.uk.app/v1/names/check?name=petersblog"
# {"name":"petersblog.uk.app","available":true,"price_gbp":4.99,"billing":"yearly, auto-renew"}
# 2. reserve it (returns a checkout URL; the name activates after payment)
curl -H "Authorization: Bearer $TOKEN" -X POST https://api.uk.app/v1/names -d name=petersblog
# 3. once active, point it at your server
curl -H "Authorization: Bearer $TOKEN" -X POST https://api.uk.app/v1/names/petersblog/records \
-d host=@ -d type=A -d content=203.0.113.7
# 4. or use a preset
curl -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -X POST https://api.uk.app/v1/names/petersblog/presets/vercel \
-d '{"params":{"target":"YOUR-PROJECT.vercel-dns-017.com"}}'
# 5. confirm DNS + HTTPS
curl -H "Authorization: Bearer $TOKEN" -X POST https://api.uk.app/v1/names/petersblog/verify
Check availability
{"name":"petersblog.uk.app","available":true,"reason":null,"price_gbp":4.99,"billing":"yearly, auto-renew"}
{"name":"paypal-login.uk.app","available":false,"reason":"Names containing protected brands need manual review. Contact support@uk.app."}
Rules: 3–63 characters, a-z 0-9 -, no leading/trailing hyphen. One- and two-character names are held back for a later release. System words, protected brands and phishing patterns are refused.
WHOIS
Registered or available, dates, status, name servers. Owner details are never returned. Add ?format=json or send Accept: application/json; otherwise you get classic whois text, handy for curl.
$ curl https://uk.app/whois/petersblog
Domain Name: petersblog.uk.app
Registry: uk.app (third-level registry, powered by PowerDNS + DNSSEC)
Status: available
Price: £4.99/year, renews automatically
Register: https://my.uk.app/dashboard?claim=petersblog
Name Servers: ns1.uk.app, ns2.uk.app
DNSSEC: signed
List, register, release
[{"name":"petersblog.uk.app","label":"petersblog","status":"active","records":4,"claimed_at":"2026-09-10T09:00:00Z","expires_at":"2027-09-10T09:00:00Z"}]
Body: {"name": "petersblog"}. Reserves the label for 48 hours and returns 201 with the name plus checkout_url. The name enters DNS the moment the first year (£4.99) is paid; agents should hand the URL to their human.
{"name":"petersblog.uk.app","status":"pending","checkout_url":"https://my.uk.app/names/42/checkout", ...}
Releases the name and removes its records from DNS. The subscription stops renewing. The label is quarantined before anyone else can register it.
Your e-mail, limits, price and the scopes of the current token.
Verify DNS & HTTPS
Resolves the name and opens a TLS connection to it, so a deploy script or an agent can confirm the site is actually reachable — including the certificate your host issued.
{"name":"petersblog.uk.app",
"dns":[{"host":"petersblog.uk.app","type":"A","ip":"76.76.21.21","ttl":300}],
"https":{"ok":true,"issuer":"Let's Encrypt","expires":"2026-12-09T08:33:00+00:00"},
"hsts_preloaded":true,"note":"Live."}
Records
| Field | Type | Notes |
|---|---|---|
host | string | @ for the name itself (default), www, *, _acme-challenge, mc.eu (nested is fine). |
type | enum | See record types. |
content | string | IP, hostname, text, or the type-specific format below. No trailing dot needed. |
ttl | int | 60–86400 seconds, default 300. |
prio | int | MX and SRV priority, default 0. |
curl -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-X POST https://api.uk.app/v1/names/petersblog/records \
-d '{"host":"_minecraft._tcp","type":"SRV","content":"5 25565 petersblog.uk.app","prio":0}'
# 201 {"id":91,"host":"_minecraft._tcp","type":"SRV","content":"5 25565 petersblog.uk.app","ttl":300,"prio":0,"source":"api"}
Every write is transactional: if the DNS server rejects a record, nothing is stored and you get a 422 with the reason. Adding a real record on @ automatically removes the parking record.
Record types
| Type | Content format | Example |
|---|---|---|
A | IPv4 | 203.0.113.7 |
AAAA | IPv6 | 2001:db8::7 |
CNAME | hostname; cannot share a host with other records | cname.vercel-dns.com |
ALIAS | hostname, resolved on our side — a CNAME that works on @ | your-site.netlify.app |
TXT | text, up to 4000 chars (quoted automatically) | v=spf1 -all |
MX | mail hostname, priority in prio | mail.protonmail.ch / prio 10 |
SRV | weight port target, priority in prio | 5 25565 petersblog.uk.app |
CAA | flags tag "value" | 0 issue "letsencrypt.org" |
NS | hostname — delegate a sub-host to another DNS provider | ns1.example-dns.com |
Presets
Replaces routing records only at the affected hosts, preserving mail, TXT and unrelated records. Conflicts return 422 without changing DNS. Delegated names must first switch to managed DNS. Parameters go in params.
Add the exact hostname to your hosting project first. For Vercel, Netlify and Pages, optional params.host selects @ (default) or www; only that hostname changes. A CNAME cannot coexist with MX or TXT at the same hostname: choose www to retain mail at @. Provider verification records must stay within your registered name.
| Preset | Params | Records created |
|---|---|---|
vercel | target | CNAME to the exact target shown in the project's Domains settings |
netlify | site | CNAME to your full site hostname, e.g. your-site.netlify.app |
cf-pages | project | CNAME to your full your-project.pages.dev hostname; add it in Pages first to avoid error 522 |
github-pages | user | 4 × @ A 185.199.108-111.153, www CNAME user.github.io |
cf-tunnel | Unavailable | Returns 422: Tunnel requires DNS records in the same Cloudflare account |
minecraft | ip, port | @ A ip, _minecraft._tcp SRV 5 port name |
server | ip | @ A ip, www CNAME name |
parking | — | back to our parking page |
curl -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-X POST https://api.uk.app/v1/names/petersblog/presets/minecraft \
-d '{"params":{"ip":"203.0.113.7","port":"25565"}}'
Own nameservers (delegation)
Body: {"nameservers": ["ns1.example-dns.com", "ns2.example-dns.com"]} (2–6 hosts, must resolve, must not be inside the name itself). Replaces every record with NS records so petersblog.uk.app and everything under it is served by your provider — Cloudflare, Route 53, deSEC, your own PowerDNS. Create the zone petersblog.uk.app there first.
Back to managed DNS on ns1/ns2.uk.app (parks the name; add records again).
Import / export
One record per line, host type content [ttl] [prio]; lines starting with ; or # are ignored. Send as zone field or as the raw request body. Import appends, so export → edit → delete → import gives you a full replace.
curl -H "Authorization: Bearer $TOKEN" https://api.uk.app/v1/names/petersblog/export
; petersblog.uk.app — exported 2026-09-10T10:00:00Z
; host type content ttl [prio]
@ A 203.0.113.7 300
www CNAME petersblog.uk.app 300
@ MX mail.example.com 3600 10
@ TXT "v=spf1 -all" 300
Bin (released names)
DELETE /v1/names/{label} moves a name to the bin: it stops resolving at once, records are kept for 30 days and the subscription keeps running so you can change your mind.
Puts the name back in DNS with its old records, as long as nobody registered it meanwhile and the subscription is still valid.
Deletes permanently: cancels the Stripe subscription, wipes the records and frees the label immediately. Not undoable.
Dynamic DNS
Standard dyndns2 protocol, so it works unchanged with ddclient, inadyn, OpenWrt, MikroTik, Synology, Fritz!Box, UniFi and most routers. Username is the name (petersblog.uk.app), password is the DDNS token from the domain page → Dynamic DNS (scoped to that name only). Create or replace it there; copy it immediately because it is shown only once. Revoke it to stop updates without deleting DNS records. DDNS requires managed DNS and an active, parked or grace-period name. Omit myip to use the caller's address; IPv6 addresses create AAAA records. Responses: good ip, nochg ip, badauth, nohost.
# /etc/ddclient.conf
protocol=dyndns2
ssl=yes
use=web
server=ddns.uk.app
login=petersblog.uk.app
password=YOUR_DDNS_TOKEN
home.petersblog.uk.app
# or from cron
curl -u "petersblog.uk.app:YOUR_DDNS_TOKEN" "https://ddns.uk.app/nic/update?hostname=home.petersblog.uk.app"
ACME DNS-01 (wildcard certificates)
Any ACME client that can call an HTTP API for the _acme-challenge TXT record works today with a write:records token. Example hook for certbot --manual or acme.sh's generic DNS API:
# auth hook: create the challenge
curl -s -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-X POST https://api.uk.app/v1/names/petersblog/records \
-d "{\"host\":\"_acme-challenge\",\"type\":\"TXT\",\"content\":\"$CERTBOT_VALIDATION\",\"ttl\":60}"
# cleanup hook: delete it again (look up the id first)
curl -s -H "Authorization: Bearer $TOKEN" https://api.uk.app/v1/names/petersblog/records \
| jq -r '.[] | select(.host=="_acme-challenge") | .id' \
| xargs -I{} curl -s -H "Authorization: Bearer $TOKEN" -X DELETE https://api.uk.app/v1/names/petersblog/records/{}
Native plugins for acme.sh, lego and certbot are on the roadmap. Until uk.app is on the Public Suffix List, Let's Encrypt counts all uk.app names against one weekly limit — if you hit it, ZeroSSL and Google Trust Services accept the same challenge.
MCP for AI agents
An MCP server at https://mcp.uk.app (Streamable HTTP, OAuth 2.1) exposes these tools: check_name, suggest_names, register_name, list_records, set_record, delete_record, apply_preset, verify_name, get_ddns_config. Claude Code, Claude Desktop, Cursor and ChatGPT connect by URL and ask you to sign in with Google. Launching in the next release; the REST API above is the same surface.
Limits & billing
| Price | £4.99 per name per year, renews automatically, same price forever. VAT added where applicable. |
| Names per account | 100 (ask support@uk.app for more). |
| Records per name | 100. |
| Reservation | 48 hours unpaid, then the label is released. |
| Lapse | 30 days grace (works) → 30 days hold (stops) → 30 days quarantine → available again. |
| Refund | 14 days on the first payment if the name never left the parking page. |
| API rate | 600 requests/minute per token; DDNS 60/minute per name. |
HTTPS & HSTS
The .app TLD is on the HSTS preload list of Chrome, Firefox, Safari and Edge. Browsers refuse plain HTTP for any .app hostname, so a uk.app site without a valid certificate does not load at all — there is no "proceed anyway". Vercel, Netlify, Cloudflare Pages, GitHub Pages and Caddy issue certificates automatically; on your own server use Let's Encrypt (HTTP-01 works as soon as the A record resolves). Use verify to confirm the certificate is in place.
OpenAPI
The machine-readable spec is at https://api.uk.app/v1/openapi.json (OpenAPI 3.1). Generate a client with openapi-generator, or paste the URL into Postman, Insomnia or Bruno.