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).

  • deliverable is true only 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 returns Y.
  • residential is Google's residential flag when Google knows it, otherwise null.
  • street1 and city are always required. state and zip are required only for US and Puerto Rico addresses; leave state out 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 example Address 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

CodeMeaning
ADDRESS.INCOMPLETEGoogle did not mark the address complete
ADDRESS.GRANULARITYMatched only to a street, city, or broader level, not a building
ADDRESS.MISSING_COMPONENTA required part is missing (e.g. apartment/suite/unit number)
ADDRESS.UNCONFIRMED_COMPONENTA part was present but could not be confirmed
ADDRESS.UNRESOLVED_INPUTInput text that could not be matched to any part of the address
USPS.NOT_CONFIRMEDUSPS DPV N: delivery point not confirmed
USPS.SECONDARY_MISSINGUSPS DPV D: building confirmed, unit number missing
USPS.SECONDARY_UNCONFIRMEDUSPS 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

OperationRate (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 AddressLookup row and are never billed (costCents: 0, cached: true).
  • Usage: every response reports costCents and freeTierRemaining.

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" }
  }
}
CodeHTTPMeaning
UNAUTHORIZED401Missing or invalid API key
FORBIDDEN403API key lacks addresses:verify scope
VALIDATION_ERROR400Bad request body, missing required fields, unsupported country, or Google rejected the address
PAYMENT_REQUIRED402Free tier used and no card on file (or billing past due); details.billingUrl links to Settings > API Keys
RATE_LIMITED429Exceeded 60 rpm
INTERNAL_ERROR502The 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: 0 and reuse the prior lookupId.

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;
}