Check a postal code
A postal code that looks fine to the eye can still break a shipping label, a tax calculation, or a CRM import. This zip code validation API checks the format of a postal code against the pattern rules of its country, catching typos and malformed entries the moment they're typed, not after the parcel bounces back.
Run — free
Runs in your browser. Free, unlimited — your data never leaves this page.
The problem with 'looks right'
Postal systems are not standardized. A US ZIP is five digits, sometimes nine with a hyphen. A Canadian code alternates letters and numbers. A UK postcode has a length that varies by region and a format that trips up even native scripts. When a checkout form or a signup page accepts anything typed into a text box, a meaningful share of addresses end up unroutable, and someone downstream has to fix it by hand. This endpoint exists to catch that before it becomes a support ticket.
What the endpoint actually checks
You send a postal code and a country identifier to POST /verify/postal-code. The task validates the string against the structural pattern used by that country's postal authority: digit counts, letter positions, separators, and known reserved ranges where applicable. It confirms the code is well-formed for that country; it does not confirm the code corresponds to a real, currently assigned delivery area, since postal boundaries change and that data is not something we fabricate or guess at.
A quick history most people never think about
Postal codes are younger than most assume. Germany introduced a numeric system in the 1940s, the United States rolled out ZIP codes in 1963, and the UK's alphanumeric postcodes were phased in gradually through the 1970s and 80s. Because each country built its system independently, to solve local sorting problems with local infrastructure, there is no universal pattern. That's precisely why format validation needs a maintained, per-country rule set rather than a single regular expression someone wrote once and forgot about.
Where it fits in your pipeline
Call it inline during checkout or signup for immediate feedback, or run it in bulk against an existing customer or shipping database to flag records worth a second look. Because every task is asynchronous, you queue the request, keep your form responsive, and receive the result through a signed webhook or a signed link valid for 24 hours. Nothing here requires you to bolt geocoding or address-completion logic onto a simple validation step.
What it's not
This is a format checker, not a full address verification suite. It won't tell you a street exists or that a courier delivers there. Used for what it is, it's a cheap, fast, precise filter that removes a large share of obviously broken entries before they cost you a failed delivery or a bounced invoice.
What you can do with it
Checkout form validation
Validate the postal code field in real time as a customer fills out shipping details, before the order is placed and the label is printed.
Customer database cleanup
Run a bulk pass over an aging CRM export to flag postal codes that no longer match the expected format for their listed country.
Multi-country signup forms
Apply the correct country-specific pattern automatically instead of maintaining your own table of regular expressions for 200+ postal systems.
Logistics pre-checks
Filter a batch of orders before handing them to a carrier API, reducing the rate of failed label generation caused by malformed input.
FAQ
What does this zip code validation API actually verify?
It checks that a postal code matches the structural format expected for the given country: correct digit or letter count, separators, and known patterns. It does not confirm the code is currently in active use.
Does it support countries outside the US?
Yes, it covers postal formats for more than 200 countries and territories, each checked against its own pattern rather than a generic rule.
Is there a free tier or trial?
The tool above runs free in your browser. The API is paid — each call draws from your prepaid ForHosting KIT balance: top up from $10.00 (it never expires), pay each request's published price, and a call with no balance returns HTTP 402. No subscription, no tokens, and a failed task is never charged.
How much does each request cost?
$0.002 per request, billed only on tasks that complete successfully.
What happens if a task fails?
The system retries automatically up to three times. If it still fails, you get a clear error and you are not charged for it.
How do I get the result back?
Every call to POST /verify/postal-code returns a task_id immediately; the result is delivered via a signed webhook, or you can fetch it from a signed link valid for 24 hours.
Can I validate postal codes in bulk?
Yes, since every task runs asynchronously, you can queue large batches without blocking your application while results come back.
Does it verify deliverability, not just format?
No, it's strictly a format check. Confirming an address is deliverable requires courier or postal-authority data this endpoint doesn't claim to have.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, and soon from our app, email and Telegram.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/verify/postal-code \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"items":["valor-1","valor-2"]}'const res = await fetch("https://api.kit.forhosting.com/verify/postal-code", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"items": [
"valor-1",
"valor-2"
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/verify/postal-code",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"items": [
"valor-1",
"valor-2"
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/verify/postal-code", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"items":["valor-1","valor-2"]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"items":["valor-1","valor-2"]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/verify/postal-code", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"items": [
"valor-1",
"valor-2"
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "verify.postal_code",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |