Australian address API documentation
WattleAddr turns a partly typed Australian address into a correct, structured one: street, suburb, state, postcode and coordinates. Base URL https://api.wattleaddr.com.au/v1. JSON only. Australian addresses only.
Before you start
You need three things.
- An account. Sign up with your name, work email and a password, then press Create account. You start on the Free plan: 5,000 lookups a month, no card.
- An API key. In the console open API Keys and press Create my first key. That makes a secret key in test mode (
waddr_sk_test_…). Test mode uses the real address data, costs nothing and never counts against your 5,000 monthly lookups, for the first 15 days of your account. The console shows the key once, under Copy your key now. Copy it somewhere safe. - A terminal with
curl, or any HTTP client. Every example below is a plain HTTPS request.
Want to choose the key yourself? Press Choose the options myself. Pick Secret (server) for code that runs on your server, or Publishable (browser) for code that runs in a web page. Pick Live or Test. A live browser key must list the domains it may run on. Never put a secret key in a web page or a public repository; use a publishable key there.
Your first request in 5 minutes
Replace YOUR_KEY with the key you just copied.
1. Ask for suggestions as someone types
Pick any unique string as the session and reuse it for every request about this one address. Autocomplete is never charged. Suggestions start from three characters.
"https://api.wattleaddr.com.au/v1/addresses/autocomplete?q=1+martin+pl&session=demo-1"
"session": "demo-1",
"query": "1 martin pl",
"suggestions": [
{ "id": "GANSW706124693", "formatted": "1 Martin Place, Sydney NSW 2000", "score": 42.66 }
]
}
The -H line sends your key in a header named Authorization; that is how a secret key is always sent. In the response, id is the address’s identifier in G-NAF, the national address dataset (see Words we use). formatted is the text to show in the dropdown. score only ranks suggestions within this one response; you can ignore it. Show the list, let the person pick one.
2. Retrieve the one they picked
Same session. This is the charged call.
"https://api.wattleaddr.com.au/v1/addresses/GANSW706124693?session=demo-1"
"id": "GANSW706124693",
"formatted": "1 Martin Place, Sydney NSW 2000",
"components": {
"flat_number": null, "level_number": null,
"street_number": "1", "street_name": "Martin", "street_type": "Place",
"locality": "Sydney", "state": "NSW", "postcode": "2000"
},
"geo": { "lat": -33.8678, "lng": 151.2073 },
"confidence": "high",
"source": "gnaf", "gnaf_release": "2026-08",
"billed": false, "test": true
}
| Field | What it is |
|---|---|
components | The address in parts. All strings, any may be null. locality is the suburb or town. Keep postcode as a string: Darwin is 0800. |
geo | Latitude and longitude, or null if the record has no coordinates. |
confidence | G-NAF’s own confidence in this record. Not a match score. |
gnaf_release | The quarterly dataset that answered. Store it with the address. |
billed | Whether this call used one lookup from your plan. false here because the key is a test key. |
test | Present, and true, only on a test key. Nothing is ever charged on one. |
3. What you were charged
Nothing yet, because this was a test key: that is what "billed": false and "test": true are telling you. On a live key the same two calls use exactly one lookup, for step 2, and billed comes back true. Every keystroke in step 1 is free because it shared the session. Leave the session out and every retrieve is charged on its own.
Every successful response on a plan with a quota also carries X-WattleAddr-Quota-Limit and X-WattleAddr-Quota-Remaining headers, so on a live key you can watch your month’s allowance drop by one.
4. Save the address properly
Store id, formatted and components together. Identifiers can change between G-NAF releases, so never keep the id on its own. There is a schema you can copy in how to store an Australian address, and the address format checker shows how a messy address splits into these fields.
Before you go live: create a Live key. Test keys stop working after the test period with 402 test_mode_expired. Live keys work on every plan, including Free.
Add it to your site
Three ways, all using a publishable key (waddr_pk_…) locked to your domains in the console. Create one under API Keys, Choose the options myself, Publishable (browser); test mode is fine while you build. Each of the three handles the session for you, so all the keystrokes behind one address count as a single lookup.
The browser widget
One script tag, no build step, no dependencies.
<script src="https://api.wattleaddr.com.au/v1/widget.js"></script>
<script>
const wa = new WattleAddr('waddr_pk_live_…');
wa.attach('#address', {
onSelect: (a) => console.log(a.formatted, a.components, a.geo)
});
</script>
| Option | Default | What it does |
|---|---|---|
onSelect | required | Called with the full record once someone picks an address. |
onError | console warning | Called instead of logging, if you want to handle failures yourself. |
theme | light | light, dark, or auto to follow the visitor’s system setting. |
limit | 6 | How many suggestions to show. |
state | all | Restrict to one state, for example NSW. |
The npm packages
Official, typed, MIT-licensed packages. Use @wattleaddr/react for React, or the zero-dependency @wattleaddr/js for vanilla JavaScript or server-side Node.
npm install @wattleaddr/react
# Vanilla JS / Node: headless client + the same drop-in widget
npm install @wattleaddr/js
The React component drops into any form:
<AddressAutocomplete
apiKey="waddr_pk_live_…"
onSelect={(a) => console.log(a.formatted, a.components.postcode)}
/>
Or call it headlessly with the client, which runs in the browser and in Node 18+. On the server, set keyTransport: 'header' to send a secret key as a Bearer token instead of in the URL:
const client = new WattleAddrClient('waddr_sk_live_…', { keyTransport: 'header' });
const session = createSession();
const { suggestions } = await client.autocomplete('1 mart', { session });
const addr = await client.retrieve(suggestions[0].id, { session }); // the charged call
Worked examples: a React form, a Next.js App Router form and a checkout with the widget.
Keys and authentication
Two key types, both created in the console. Never expose a secret key in the browser.
| Key | Prefix | Where | Secured by |
|---|---|---|---|
| Secret | waddr_sk_live_… | Server-side | Authorization: Bearer + optional IP allowlist |
| Publishable | waddr_pk_live_… | Browser (autocomplete and retrieve only) | The website domains you list + rate limits |
Either type can be made in test mode (waddr_sk_test_…, waddr_pk_test_…): real data, never charged, free for the first 15 days of an account. Responses from a test key carry "test": true.
That is how your application authenticates. How your team signs in to the console is separate: on Enterprise plans you can point it at your own identity provider over OpenID Connect, and require it. See single sign-on setup.
What you are charged
Billing is session-based, the same model as Google Places: send a session token with each autocomplete keystroke and the final retrieve, and the whole session counts as one lookup, however many keystrokes it took. Autocomplete on its own is never charged. Retrieve or verify with no session token always charges, so always send one. A verify that finds no match is not charged.
Exhausting a plan’s monthly quota returns 402 quota_exceeded; a per-key burst limit returns 429 rate_limited with a Retry-After header. Both, and every other code, are in common errors. Plan allowances and prices are on the pricing page.
Limits and performance
Every key has a requests-per-second limit set by its plan. It is a token bucket: a key can send that many requests at once, then continues at that rate. Over it, the API returns 429 rate_limited with a Retry-After header saying how many seconds to wait. Nothing is queued, and a refused request is neither counted nor charged.
| Plan | Requests per second, per key | Live keys per workspace |
|---|---|---|
| Free | 5 | 2 |
| Starter | 15 | 10 |
| Growth | 40 | 25 |
| Enterprise | 200 | 1,000 |
There is no separate concurrency limit: parallel requests are fine within the per-second rate. Each key has its own bucket, and the right setup is one key per system, each with its own limit. If one system needs more than its plan’s rate, talk to us rather than fanning out across keys; limits above the published ones are set by agreement. Test keys have a separate, smaller allowance. Monthly volume is governed separately by the plan’s quota (above).
Latency. Measured inside the API in September 2026: autocomplete about 65 ms at the median and about 225 ms at the 95th percentile; verify 35 to 100 ms once a query is warm, and up to about 600 ms on the first call for a large block of units. On top of that, a fresh HTTPS connection from within Australia adds roughly 50 to 90 ms and a reused one about 45 ms, so keep connections alive. Repeated prefixes are served from an in-memory cache. Live availability and the G-NAF release being served are on the status page.
Under the hood: the full G-NAF release, loaded into OpenSearch by the open-source Addressr loader that we host and queried directly by our API (the exact query first, a spelling-tolerant one only when that finds nothing); the same API does keys, metering, scoring and the corrections described below; Postgres for keys, usage and billing. Sydney is primary, Melbourne a warm standby, and nothing leaves Australia. Quarterly G-NAF releases are loaded in place with no downtime.
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /v1/addresses/autocomplete | Type-ahead suggestions for a partial query |
GET | /v1/addresses/{id} | Full structured record for a chosen suggestion (charged) |
POST | /v1/addresses/verify | Match free-text to its official G-NAF form, with a per-field verdict (charged on a match) |
GET | /v1/status | Service health & current G-NAF release |
Every parameter and response schema is in the interactive reference.
Example: verify, and what a verdict means
There is a plain-language overview of verification, and what it is not, on the verification page.
verify takes free text you already hold and returns the best match in its official G-NAF form, plus an assessment of how well it answers what you sent. Branch on verdict.
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"address": "23 wakool ave deer park", "session": "demo-2"}'
"matched": true,
"match": { /* the full record, same shape as retrieve */ },
"verdict": "corrected",
"match_score": 75,
"match_level": "premise",
"elements": { "street_number": "verified", …, "postcode": "missing" },
"changed_elements": [],
… plus alternatives, source, gnaf_release, billed and, on a test key, test
}
verdict | Meaning | Handling |
|---|---|---|
verified | Every element you supplied agrees, and no other candidate fits | Accept |
corrected | Agrees as far as it goes; you abbreviated or left something out | Accept the official form, or re-prompt for the missing elements you need (missing means not supplied, not required: a house has no unit) |
ambiguous | An element was contradicted, input was discarded, a runner-up fits equally well, or you gave a building address and the building has units (the alternatives list them) | Review, never auto-accept. If your data is deliberately at building level, accept on match_level: premise and check alternatives |
unverified | Nothing matched, or the match scored too low to rely on | Reject and flag |
elements gives every field its own status (verified, changed or missing), so a checkout can re-prompt for the one field that is wrong instead of the whole address. match_level says how precisely we identified it (subpremise a unit, premise a building, thoroughfare the street only, locality the suburb only) and is independent of the verdict. When you supply no unit or level, verify prefers the building over its units; supply the unit to get subpremise. We publish no accept/reject score threshold: the right line depends on what a wrong address costs you. The older top-level confidence field on a verify response is deprecated; branch on verdict. A verified address exists in G-NAF and we know where it is; that is not a promise that mail can be delivered to it. There is a worked example of verifying server-side, without paying twice, in the Next.js guide.
Typos, misspellings and units
Matching is fuzzy but not phonetic. A word of three to five letters may have one character wrong, missing, extra or swapped; a longer word may have two; a word of one or two letters must be exact. That applies to the street name and the suburb alike, with one exception: the last word typed is treated as a prefix and gets no tolerance. So 589 Beems Rd Carseldine and 589 Beams Rd Carsledine QLD both find 589 Beams Rd, but a suburb misspelt at the very end of the query matches nothing on its own. When nothing matches, the trailing words (suburb, state, postcode) are dropped one at a time and the search retried; verify reports what it discarded in dropped_tokens and scores the match lower. Suggestions appear once the query names a street or suburb; a bare unit, level or small street number on its own returns an empty list.
Verify never corrects a street number. A number that disagrees with the record is marked changed, not swapped. Autocomplete is looser: a one- or two-digit number is matched exactly, so if no premise carries it the suggestions are sub-dwellings that do (8 Beams Rd returns Unit 8 of the blocks on Beams Rd); a number of three or more digits gets the same one-character tolerance as a word, so a number that does not exist on the street can bring up its neighbours. When you type a number and no unit, the premise with that number on that street comes before anybody’s units, and a building normally comes before its own units even when the block is large. Type the unit (3/589, unit 3 589) to get the unit.
Verify tells you what it changed. elements marks each part verified, changed or missing, and changed_elements lists the corrected ones; the corrected values are in match.components. A corrected street name lowers match_score and gives an unverified or ambiguous verdict, never verified, so a misspelt street reaches you as the right record flagged for review, not as an automatic accept. A building address on a block with units matches the building with an ambiguous verdict and some of its units in alternatives.
PO Boxes & postal addresses
G-NAF is a register of physical addresses, so it contains no PO Boxes, GPO Boxes, Locked Bags or Private Bags. Rather than returning a confusing empty list, a query for one returns 200 with an empty suggestions array and a notice, so your form can prompt for a street address or accept the PO Box in a separate field. verify returns the same notice with matched: false and billed: false. A query the dataset cannot answer is never charged. If you need PO Boxes validated, you need a product licensed for Australia Post’s PAF; AMAS and PAF vs G-NAF explains the boundary.
"code": "postal_address_unsupported",
"kind": "PO Box",
"message": "PO Box addresses aren’t in the G-NAF dataset…"
} }
Common errors
Every error is JSON: { "error": { "code", "message", "docs" } }. The code is stable; switch on it. The message may change. The docs link points at the matching row of the full error reference.
| Status | code | What happened | What to do |
|---|---|---|---|
| 400 | bad_request | The query (q) or address is under 3 characters, or over the length limit. | Wait until the person has typed 3 characters before calling. Trim very long input. |
| 401 | unauthorized | No API key was sent. | Add an Authorization: Bearer YOUR_KEY header (secret key), or ?key= for a publishable key in the browser. |
| 401 | invalid_key | The key is wrong, has been revoked, or has a typo. | Copy it again from the console, or create a new key. Revoked keys never come back. |
| 402 | quota_exceeded | This month’s lookups are used up and your plan does not run over (Free, Shopify, a cancelled plan, or a paid plan with a spending cap set at your request), or a paid plan has reached the overage ceiling of five times its included lookups. The message says which. | Upgrade in the console under Billing, or wait for the period to roll over. If the message says the overage ceiling was reached, contact support to lift it; a plan change only takes effect next period. Autocomplete keeps working; only charged calls are refused. |
| 402 | test_mode_expired | A test key was used after the free test period (15 days from account creation). | Create a Live key in the console. Live keys work on every plan, including Free. |
| 402 | feature_unavailable | The endpoint is not included in the workspace’s plan. | Check the plan in the console under Billing. |
| 403 | forbidden_referrer | A publishable key was used from a domain that is not on its allowed list. | Edit the key in the console and add the domain to Allowed domains. Match the exact host the page is served from. |
| 403 | forbidden_key_type | A publishable (browser) key was used to call verify. Publishable keys can only call autocomplete and retrieve. | Call verify from your server with a secret key. If a person is typing the address, use autocomplete and retrieve with the publishable key instead; verify is for addresses you already hold. |
| 403 | forbidden_ip | A secret key was used from an IP address that is not on its allowed list. | Edit the key in the console and add the address to Allowed IPs / CIDRs, or clear the list to allow any IP. |
| 404 | not_found | No address has that id. | Use an id exactly as it came back from an autocomplete suggestion. Ids can change between G-NAF releases, so re-search rather than replaying an old one. |
| 429 | rate_limited | Too many requests per second on one key. | Wait the number of seconds in the Retry-After header, then retry. Debounce keystrokes in the browser. |
Not an error: 200 with "suggestions": [] and a notice. See PO Boxes above.
Building with a coding agent
If you use Claude Code, Cursor, Copilot or similar, give it our agent skill. It covers the things an assistant otherwise gets wrong: session tokens and what they cost, which key belongs in the browser, PO Boxes being absent from G-NAF by design, reading a verify verdict, and how to store the address components. It is a single markdown file with no telemetry.
curl -o WATTLEADDR.md https://wattleaddr.com.au/skills/wattleaddr/SKILL.md
# Claude Code: drop it in as a skill
mkdir -p .claude/skills/wattleaddr && curl -o .claude/skills/wattleaddr/SKILL.md \
https://wattleaddr.com.au/skills/wattleaddr/SKILL.md
It also ships inside @wattleaddr/js, at node_modules/@wattleaddr/js/skills/wattleaddr/SKILL.md, so an agent working in a project that already depends on the SDK can find it without downloading anything.
Words we use
| Word | Meaning |
|---|---|
| endpoint | One URL the API answers on. There are four: autocomplete, retrieve, verify and status. |
| autocomplete | The call that returns suggestions as someone types. Never charged. |
| retrieve | The call that returns the full record for one suggestion. Charged once per session. |
| verify | The call that matches free text you already hold to its official form. Charged on a match. |
| lookup | The unit you are charged in. One lookup is one address a person actually picked (retrieve) or one free-text address matched (verify). The Free plan includes 5,000 a month. |
| session | Any string you choose that ties the keystrokes for one address to the final retrieve. Shared session means one lookup. No session means every retrieve is a lookup. |
| quota | The lookups included in your plan each month. When they are used up, charged calls return 402 on plans that do not run over. |
| rate limit | How many requests per second one key may make. Over it, the API returns 429 with Retry-After. |
| secret key | waddr_sk_…. For your server, sent as Authorization: Bearer. Never in a web page. |
| publishable key | waddr_pk_…. For the browser. Safe to be public because it only works from the domains you list. |
| test key | waddr_sk_test_… or waddr_pk_test_…. Real data, never charged, time limited. |
| G-NAF | The Geocoded National Address File: the open register of every physical address in Australia, published quarterly by Geoscape. WattleAddr is built on it. It has no PO Boxes. |
| locality | G-NAF’s word for suburb or town. |
| verdict | Verify’s answer on what to do with a match: verified, corrected, ambiguous or unverified. Branch on this, not on confidence. |
| notice | A note on a successful response explaining why it is empty. Not an error. |
Data & privacy
Every request is served from Australian infrastructure. No query or matched address is processed, cached or routed offshore, and your end-users’ input never touches a third-party ad platform such as Google Places.
Autocomplete queries and the addresses they match are recorded in a per-workspace search log for usage, debugging and support. In the console you choose what it keeps:
| Mode | What it stores |
|---|---|
full (default) | Query and matched-address text, in full, until retention purges it. |
hashed | Each query as a salted keyed hash; matched-address text is dropped. This is pseudonymisation, not anonymisation: we hold the key. |
none | No query or matched-address text at all; only counts, timing and billing. |
Retention is configurable (7–365 days, by plan) and old query text is purged automatically; you can also apply a privacy mode to logs already stored, in one click. Why hashing an address is pseudonymisation explains the distinction. See the FAQ and Privacy Policy for detail.
Attribution
WattleAddr is built on the open Geocoded National Address File. The G-NAF licence requires attribution. Display this wherever you surface address data:
licensed under the Open G-NAF End User Licence Agreement.
Modified by [your product] for search and display.
Explore every endpoint and try calls live in the interactive reference. Questions? hello@wattleaddr.com.au