Address Verification API
Address verification, batch verification, and parsing for ShipWave users, as a standalone
API. All endpoints under /api/v1/addresses/* are billable (first 100 lookups per
organization per calendar month are free).
Verification Provider
verify and verify-batch use Google Address Validation. US and Puerto Rico
addresses are also checked against USPS data, so the response uses
USPS-standardized fields (uppercase, ZIP+4) and USPS delivery-point validation
(DPV).
deliverableistrueonly when Google marks the address complete, confirms every component, matches it to a building or unit (PREMISE/SUB_PREMISE), and, for US/PR, USPS DPV returnsY.residentialis Google's residential flag when Google knows it, otherwisenull.street1andcityare always required.stateandzipare required only for US and Puerto Rico addresses; leavestateout for countries that don't use one (for example{"street1":"10 Downing Street","city":"London","zip":"SW1A 2AA","country":"GB"}).- Supported countries: the 40 countries/regions on Google's
coverage list.
Any other country returns
400 VALIDATION_ERROR(for exampleAddress could not be verified: Unsupported region code: "AF".). - Only send real addresses. The USPS data provider can cut off validation for accounts that send made-up US addresses, including for testing.
messages codes
| Code | Meaning |
|---|---|
ADDRESS.INCOMPLETE | Google did not mark the address complete |
ADDRESS.GRANULARITY | Matched only to a street, city, or broader level, not a building |
ADDRESS.MISSING_COMPONENT | A required part is missing (e.g. apartment/suite/unit number) |
ADDRESS.UNCONFIRMED_COMPONENT | A part was present but could not be confirmed |
ADDRESS.UNRESOLVED_INPUT | Input text that could not be matched to any part of the address |
USPS.NOT_CONFIRMED | USPS DPV N: delivery point not confirmed |
USPS.SECONDARY_MISSING | USPS DPV D: building confirmed, unit number missing |
USPS.SECONDARY_UNCONFIRMED | USPS DPV S: building confirmed, unit number not confirmed |
Authentication & Scope
Address verification endpoints require an API key with Address verification
access (scope addresses:verify). Create one in the dashboard under
Settings > API Keys and check Address verification. Shipping API
access (read/write) alone is not sufficient — these surfaces are opt-in
to prevent accidental metered usage.
curl -H "Authorization: Bearer sw_live_abc..." \
-H "Content-Type: application/json" \
https://shipwave.app/api/v1/addresses/verify
Pricing
| Operation | Rate (after free tier) |
|---|---|
verify (single) | $0.02 / lookup |
verify-batch (per entry) | $0.02 / lookup |
parse | $0.005 / lookup |
- Free tier: the first 100 new lookups per organization per calendar month (UTC), across verify, verify-batch, and parse.
- Past the free tier: add a card in ShipWave under Settings > API Keys. Usage is
billed monthly through Stripe. Without a card, lookups return
402 PAYMENT_REQUIRED(batch entries fail individually) until the next month. - Cache: Identical inputs within a 24-hour window reuse the prior
AddressLookuprow and are never billed (costCents: 0,cached: true). - Usage: every response reports
costCentsandfreeTierRemaining.
Rate Limits
- 60 requests per minute per API key, with a burst of 10.
- Standard rate-limit headers are returned on every response:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset.
Endpoints
Verify a Single Address
POST /api/v1/addresses/verify
Request Body
{
"address": {
"name": "Jane Smith", // optional
"company": "Acme Co", // optional
"street1": "1600 Pennsylvania Ave NW",
"street2": "Suite 100", // optional
"city": "Washington",
"state": "DC", // required for US/PR only
"zip": "20500", // required for US/PR only
"country": "US" // optional, defaults to "US"
}
}
Response
{
"data": {
"verified": {
"street1": "1600 PENNSYLVANIA AVE NW",
"street2": null,
"city": "WASHINGTON",
"state": "DC",
"zip": "20500-0005",
"country": "US",
"deliverable": true,
"residential": false,
"correctedFields": ["zip"],
"messages": []
},
"cached": false,
"lookupId": "clxyz...",
"costCents": 0,
"freeTierRemaining": 87
}
}
Example
curl -X POST https://shipwave.app/api/v1/addresses/verify \
-H "Authorization: Bearer sw_live_..." \
-H "Content-Type: application/json" \
-d '{
"address": {
"street1": "1600 Pennsylvania Ave NW",
"city": "Washington",
"state": "DC",
"zip": "20500"
}
}'
Batch Verify (up to 100)
POST /api/v1/addresses/verify-batch
Each address is verified independently and billed individually. The endpoint returns
200 even on per-entry failures — check results[i].ok for status.
Request Body
{
"addresses": [
{ "street1": "...", "city": "...", "state": "CA", "zip": "94103" },
{ "street1": "...", "city": "...", "state": "NY", "zip": "10001" }
]
}
Maximum 100 addresses per call. Larger batches return a 400 VALIDATION_ERROR.
Response
{
"data": {
"results": [
{
"index": 0,
"ok": true,
"verified": { /* VerifiedAddress */ },
"cached": false,
"lookupId": "clxyz...",
"costCents": 2
},
{
"index": 1,
"ok": false,
"error": { "code": "VALIDATION_ERROR", "message": "Missing required address fields: state", "fields": ["state"] }
}
],
"summary": {
"total": 2,
"ok": 1,
"errors": 1,
"totalCostCents": 2,
"freeTierRemaining": 86
}
}
}
Parse a Free-Text Address String (US only)
POST /api/v1/addresses/parse
Phase 1 supports US-formatted strings via a regex parser. Non-US strings return a
low confidence score and country: 'US' — international parsing will integrate
libpostal in a follow-up release.
Request Body
{ "raw": "1600 Pennsylvania Ave NW, Washington, DC 20500" }
Response
{
"data": {
"parsed": {
"name": null,
"company": null,
"street1": "1600 Pennsylvania Ave NW",
"street2": null,
"city": "Washington",
"state": "DC",
"zip": "20500",
"country": "US",
"confidence": 0.85,
"warnings": []
},
"cached": false,
"lookupId": "clxyz...",
"costCents": 0.5,
"billedRate": "0.5¢/parse",
"freeTierRemaining": 86
}
}
confidence is 0.0–1.0. Values below 0.6 mean one or more fields couldn't be
identified cleanly — re-prompt the user before calling /verify.
Errors
All errors follow the standard envelope:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Missing required address fields: zip",
"details": { "fields": "zip" }
}
}
| Code | HTTP | Meaning |
|---|---|---|
UNAUTHORIZED | 401 | Missing or invalid API key |
FORBIDDEN | 403 | API key lacks addresses:verify scope |
VALIDATION_ERROR | 400 | Bad request body, missing required fields, unsupported country, or Google rejected the address |
PAYMENT_REQUIRED | 402 | Free tier used and no card on file (or billing past due); details.billingUrl links to Settings > API Keys |
RATE_LIMITED | 429 | Exceeded 60 rpm |
INTERNAL_ERROR | 502 | The verification provider was unavailable; retry shortly |
Caching Semantics
- Cache key:
SHA-256(street1 + street2 + city + state + zip + country)(case-insensitive, whitespace-trimmed). - TTL: 24 hours from initial verification.
- Per-organization scope: tenant A's cache hits are never served to tenant B.
- Cache hits return
cached: true, costCents: 0and reuse the priorlookupId.
Programmatic Usage
async function verifyAddress(input) {
const res = await fetch("https://shipwave.app/api/v1/addresses/verify", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SHIPWAVE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ address: input }),
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error?.message}`);
return (await res.json()).data;
}