Get UK Address API
One REST endpoint. Bearer auth. JSON in, JSON out. Errors follow RFC 9457 Problem Details. If you've used a typed HTTP API before, you already know how this works.
Address lookup
GET/api/v1/autocomplete
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| query | string | Yes | Free-text, 3+ chars. |
| api_key | string | Yes | Your API key, prefixed gua_. Generate from the dashboard. |
Results are capped at 100 per request — that's the maximum number of addresses that can share a single UK postcode.
Response shape
{ "requestId": "00000001", "results": [ { "organisation": "", "addressLine1": "Buckingham Palace", "addressLine2": "", "town": "London", "postcode": "SW1A 1AA", "summaryLine": "Buckingham Palace, London, SW1A 1AA" } ]}Authentication
Pass your API key as a Bearer token in the Authorization query parameter. Keys are environment-scoped — create separate keys for dev, staging and prod from your dashboard.
curl "https://getukaddress.com/api/v1/autocomplete?query=SW1A+1AA&api_key=gua_your_key"Hiding the API key
Never embed your API key in browser-side JavaScript. Anything that ships to the browser — minified bundles, environment variables prefixed VITE_ / NEXT_PUBLIC_ / REACT_APP_, query strings in fetch calls — is visible to anyone who opens DevTools. A leaked key is someone else's quota; once it's published you have to revoke and reissue.
The right pattern is a thin proxy on your own server. Your frontend calls your backend, your backend appends the API key and calls Get UK Address. The key never leaves your server.
❌ Don't — key in browser code
// Anyone opening DevTools sees gua_live_... in the network tab.const res = await fetch( `https://getukaddress.com/api/v1/autocomplete` + `?query=${q}&api_key=gua_your_key`);✅ Do — proxy through your backend
// Hits your own server. No key in client code.const res = await fetch(`/api/addresses?query=${q}`);const KEY = process.env.GETUKADDRESS_API_KEY; // server env, not exposedapp.get("/api/addresses", async (req, res) => { const q = encodeURIComponent(req.query.query ?? ""); const upstream = await fetch( `https://getukaddress.com/api/v1/autocomplete?query=${q}&api_key=${KEY}` ); res.status(upstream.status).json(await upstream.json());});Same idea applies to Next.js / SvelteKit / Nuxt route handlers, Cloudflare Workers, or any serverless function — keep the key in the runtime environment, expose only a thin endpoint to the browser. Mobile apps are no exception: binaries can be reverse-engineered, so route mobile traffic through your backend too.
If a key ever does leak, revoke it from the dashboard and issue a fresh one — the old key stops working immediately.
Error envelope
Every 4xx/5xx response is shaped like RFC 9457 Problem Details, with two extensions: requestId for support traceability, and code for machine-readable client handling.
{ "type": "https://getukaddress.com/errors/rate-limit-exceeded", "title": "Rate Limit Exceeded", "status": 429, "code": "RATE_LIMIT_EXCEEDED", "requestId": "req_a1b2c3d4"}Error codes
| Code | HTTP | When |
|---|---|---|
| INVALID_API_KEY | 401 | Key not found or revoked |
| EMAIL_NOT_VERIFIED | 403 | Account email unconfirmed |
| ACCOUNT_SUSPENDED | 403 | Account suspended by admin |
| SUBSCRIPTION_EXPIRED | 403 | Plan has lapsed |
| RATE_LIMIT_EXCEEDED | 429 | Daily quota exhausted |
| INVALID_REQUEST | 400 | Missing or malformed parameters |
| INTERNAL_ERROR | 500 | Unhandled server error |
Rate limits
Every response includes three headers so you can throttle client-side:
X-RateLimit-Limit: 500X-RateLimit-Remaining: 423X-RateLimit-Reset: 1712012400Limits reset at 00:00 UTC. When you hit zero, you get a 429 with the RATE_LIMIT_EXCEEDED code.