uk.app

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:

StatusMeaning
pendingReserved for 48 hours, not yet paid. Not in DNS.
parkedPaid, no records of your own yet — shows the parking page.
activePaid and pointing somewhere.
graceRenewal failed; keeps working for 30 days.
holdStopped resolving; restorable by paying.
suspendedTaken 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.

ScopeAllows
readList names and records, verify, WHOIS, account info.
write:recordsAdd, change, delete records; apply presets. This is all an ACME client or DDNS updater needs.
write:namesRegister 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 petersblog or petersblog.uk.app; both work everywhere.
  • Request bodies: JSON (Content-Type: application/json) or form-encoded. Responses are JSON unless noted.
  • Validation problems return 422 with {"message": "...", "errors": {"field": ["..."]}}.
  • Rate limit: 600 requests per minute per token. Headers X-RateLimit-Remaining and Retry-After tell you where you stand.
  • Timestamps are ISO 8601 in UTC.
CodeWhen
401Missing or invalid token.
403Token lacks the scope.
404Name or record not found, or not yours.
422Invalid input, name unavailable, limit reached, name not active yet.
429Rate 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

GET /v1/names/check?name=label public
{"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

GET /v1/whois/label public · JSON or text

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

GET /v1/names read
[{"name":"petersblog.uk.app","label":"petersblog","status":"active","records":4,"claimed_at":"2026-09-10T09:00:00Z","expires_at":"2027-09-10T09:00:00Z"}]
POST /v1/names write:names

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", ...}
GET /v1/names/label read
DELETE /v1/names/label write:names

Releases the name and removes its records from DNS. The subscription stops renewing. The label is quarantined before anyone else can register it.

GET /v1/me read

Your e-mail, limits, price and the scopes of the current token.

Verify DNS & HTTPS

POST /v1/names/label/verify read

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

GET /v1/names/label/records read
POST /v1/names/label/records write:records
PUT /v1/names/label/records/id write:records
DELETE /v1/names/label/records/id write:records
FieldTypeNotes
hoststring@ for the name itself (default), www, *, _acme-challenge, mc.eu (nested is fine).
typeenumSee record types.
contentstringIP, hostname, text, or the type-specific format below. No trailing dot needed.
ttlint60–86400 seconds, default 300.
priointMX 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

TypeContent formatExample
AIPv4203.0.113.7
AAAAIPv62001:db8::7
CNAMEhostname; cannot share a host with other recordscname.vercel-dns.com
ALIAShostname, resolved on our side — a CNAME that works on @your-site.netlify.app
TXTtext, up to 4000 chars (quoted automatically)v=spf1 -all
MXmail hostname, priority in priomail.protonmail.ch / prio 10
SRVweight port target, priority in prio5 25565 petersblog.uk.app
CAAflags tag "value"0 issue "letsencrypt.org"
NShostname — delegate a sub-host to another DNS providerns1.example-dns.com

Presets

POST /v1/names/label/presets/preset write:records

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.

PresetParamsRecords created
verceltargetCNAME to the exact target shown in the project's Domains settings
netlifysiteCNAME to your full site hostname, e.g. your-site.netlify.app
cf-pagesprojectCNAME to your full your-project.pages.dev hostname; add it in Pages first to avoid error 522
github-pagesuser4 × @ A 185.199.108-111.153, www CNAME user.github.io
cf-tunnelUnavailableReturns 422: Tunnel requires DNS records in the same Cloudflare account
minecraftip, port@ A ip, _minecraft._tcp SRV 5 port name
serverip@ A ip, www CNAME name
parkingback 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)

PUT /v1/names/label/nameservers write:records

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.

DELETE /v1/names/label/nameservers write:records

Back to managed DNS on ns1/ns2.uk.app (parks the name; add records again).

Import / export

GET /v1/names/label/export read · text/plain
POST /v1/names/label/import write:records

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.

GET /v1/bin read
POST /v1/bin/label/restore write:names

Puts the name back in DNS with its old records, as long as nobody registered it meanwhile and the subscription is still valid.

DELETE /v1/bin/label write:names

Deletes permanently: cancels the Stripe subscription, wipes the records and frees the label immediately. Not undoable.

Dynamic DNS

GET https://ddns.uk.app/nic/update?hostname=host&myip=ip Basic auth

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 account100 (ask support@uk.app for more).
Records per name100.
Reservation48 hours unpaid, then the label is released.
Lapse30 days grace (works) → 30 days hold (stops) → 30 days quarantine → available again.
Refund14 days on the first payment if the name never left the parking page.
API rate600 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.