Subscription
An online store
- You send
X-API-Key: sk_live_…, the key shown at signup, or regenerated in your dashboard.- You pay
- The monthly quota of your plan. Nothing is charged per call.
DOCSIntegrate the Address Validator API
From API key to production: authentication, contract, return codes, cache and quotas. Then four practical cases: direct call, CSV import, Shopify and WooCommerce.
Getting started
sk_live_…) is shown only once. After that, it can be regenerated from your dashboard.POST /validate with the key in the X-API-Key header.cache_key.curl -sS https://kder-ai.com/validate \
-H "X-API-Key: sk_live_YOUR_KEY" \
-H "content-type: application/json" \
-d '{"address": {"street": "1 Rue de Paris", "city": "Saint-Denis", "state": "RE", "zip": "97400", "country": "RE"}}'
In every example, replace sk_live_YOUR_KEY with your key.
No account yet? Test an address for free: the page stores nothing and shows you exactly what the API returns.
Access
Two ways in, one response. Only the header you send and the payment differ.
Subscription
X-API-Key: sk_live_…, the key shown at signup, or regenerated in your dashboard.Pay per call · x402
X-PAYMENT: a signed EIP-3009 authorization, built from the body of the 402 response (accepts field).X-Payment-Mode.Never put the key in a git repository, a shared file or a URL. It lives in an environment variable or a secrets manager, and only travels over https. A leaked key is regenerated in one click: the old one stops working immediately.
Reference
POST https://kder-ai.com/validate, JSON body, content-type: application/json header.
{"address": {
"street": "1 Rue de Paris", // required
"city": "Saint-Denis", // required
"state": "RE", // required (state or region)
"zip": "97400", // required
"country": "RE" // optional, "US" by default (ISO 2 letters)
}}
200{
"result": {
"validated": true,
"standardized": {"line1": "1 RUE DE PARIS", "line2": "", "city": "SAINT-DENIS",
"state": "RE", "zip": "97400", "country": "RE"},
"latitude": -20.8823, "longitude": 55.4504,
"deliverability": "residential",
"confidence": 0.99,
"degraded": false,
"cache_key": "9b7a..."
},
"price_paid": 0.0,
"transaction_id": null
}
validatedfalse is not always a verdict: read degraded before drawing a conclusion.deliverabilityresidential, commercial, unknown or invalid: the provider's code, translated into plain words.confidencedegradedtrue: the provider did not answer (outage, timeout, open circuit breaker). The response says nothing about the address. Never record it as an "invalid address".price_paid0.0 for a call covered by a subscription; the price in USDC for a paid call.cache_keyThese are the headers the route actually sets. No others are promised.
X-Payment-Modeapi_key or x402: how the call was paid.X-Plantrial, starter, pro or enterprise. Key mode only.X-Quota-RemainingReference
Eight possible codes on /validate. The three key-mode rejections (401, 402, 403) are distinct on purpose: handle each in its own way.
Success
Read result. Keep cache_key: it is your pseudonym for this address and is used to request deletion.
Nothing to retry
Fix on your side
The key is incomplete, revoked (regenerated) or meant for another service. Fix the environment variable. Do not retry in a loop.
No: fix the key
Limit or payment
Read the response body: it says which of the two cases applies. A quota 402 is not an outage.
Depends on the body
{"reason": "quota_atteint"}Usage cap reached. This is not an outage. The counter resets on the 1st of the month (UTC).{"x402Version": 1}Payment is missing for this call. The price is in the accepts field.Fix on your side
The free trial is over and the account is still on the trial plan. Move to a paid plan: nothing is charged automatically.
No: change plan
Fix on your side
A field is missing or empty. street, city, state and zip are required; country defaults to US.
After fixing it
Limit
Applies to /validate-form, the trial without an account, not the paid API. Wait for the delay given in Retry-After, or create an account.
After Retry-After
Service-side incident
The service refuses to serve a validation it cannot record. Try again. If the call was paid, quote transaction_id: reconciliation is done by hand.
Yes, keep transaction_id
Service-side incident
The key could not be verified (this is not a rejection), or the database is not responding. Try again in a few minutes: nothing was consumed.
A 503 is never a payment request.
Yes, in a few minutes
Billing
cache_key guarantees this.degraded result is kept for 5 minutes, not 30 days: "the provider did not answer" is not a verdict.degraded: true as "try again later". After this short window, the same call gets a real answer.X-Quota-Remaining. Write to us before the limit rather than after.price_paid: 0.0.402 body (accepts): the price is there, never to be guessed./validate-form.Examples
Four ways to plug in the API. Each example reads the key from an environment variable.
$cle = $env:ADDRESS_VALIDATOR_KEY
$corps = '{"address":{"street":"1 Rue de Paris","city":"Saint-Denis","state":"RE","zip":"97400","country":"RE"}}'
$r = Invoke-WebRequest -Uri https://kder-ai.com/validate -Method POST `
-Headers @{ "X-API-Key" = $cle } -ContentType "application/json" -Body $corps
$r.StatusCode
$r.Headers["X-Payment-Mode"], $r.Headers["X-Plan"], $r.Headers["X-Quota-Remaining"]
($r.Content | ConvertFrom-Json).result
"""Checks a CSV of addresses and writes the verdict next to each one.
The provider is queried only once per address: the response is cached
(30 days for a verdict), so a second run on the same file
costs nothing more.
"""
import csv
import os
import time
import requests
CLE = os.environ["ADDRESS_VALIDATOR_KEY"] # never in the code, never in git
API = "https://kder-ai.com/validate"
with open("colis.csv", newline="", encoding="utf-8") as source, open(
"colis-verifies.csv", "w", newline="", encoding="utf-8"
) as cible:
lecteur = csv.DictReader(source)
champs = list(lecteur.fieldnames) + [
"validated", "deliverability", "degraded", "note",
]
sortie = csv.DictWriter(cible, fieldnames=champs)
sortie.writeheader()
for ligne in lecteur:
reponse = requests.post(
API,
headers={"X-API-Key": CLE},
timeout=30,
json={
"address": {
"street": ligne["adresse"],
"city": ligne["ville"],
"state": ligne["region"],
"zip": ligne["code_postal"],
"country": ligne.get("pays") or "US",
}
},
)
if reponse.status_code in (500, 503):
time.sleep(5) # service-side outage: nothing was consumed
continue
if reponse.status_code == 429:
time.sleep(int(reponse.headers.get("Retry-After", "60")))
continue
if reponse.status_code != 200:
sortie.writerow({**ligne, "note": f"rejected {reponse.status_code}"})
continue
resultat = reponse.json()["result"]
sortie.writerow(
{
**ligne,
"validated": resultat["validated"],
"deliverability": resultat["deliverability"],
"degraded": resultat["degraded"],
"note": "to re-check (provider unreachable)"
if resultat["degraded"]
else "",
}
)
What the example shows, and nothing else: the key comes from an environment variable, the 429 is respected (the delay is read, not guessed), and a degraded result is marked "to re-check" instead of being written as an invalid address.
orders/create webhook (or your package-preparation step) calls your service, which queries /validate with the order's shipping address.shipping_address.address1 → street (add address2 to line2 on the result side)city → cityprovince_code → statezip → zipcountry_code → countryvalidated: false: flag the order in your back office before shipping. degraded: true: block nothing, schedule another check.woocommerce_checkout_order_processed action (order paid), or a cron job that goes through orders being prepared (wc_get_orders( ['status' => 'processing'] )).$order->get_shipping_address_1() → street$order->get_shipping_city() → city$order->get_shipping_state() → state$order->get_shipping_postcode() → zip$order->get_shipping_country() → countrycache_key with the order: it is the only address identifier valid for a deletion request.In every case: one call per address and per order, never in a loop that ignores the return codes. A quota 402 is not an outage: it is a limit, and it is read from the response.
Developers and AI agents
An autonomous agent needs neither an account nor a key. Each validation charged costs 0.02 USDC, paid with x402 on the Base network.
POST /validate with the usual body. Without payment, the response is a 402 whose body carries {"x402Version": 1} and the accepts field: the price is there.accepts, and resend the request with the X-PAYMENT header.X-Payment-Mode is x402, price_paid gives the price in USDC, and transaction_id is the reference to quote in case of a 500.For your agents, everything can be discovered without paying:
Good to know before you integrate
degraded: true Re-checkcache_key) and the outcome, never the address itself. For the free trial without an account: nothing, only an anonymous counter that enforces the cap. A deletion request is made with the cache_key.Create an account to get your key, or test an address first without an account.