fakeaddresses
v0.2.0
Published
Typed client + CLI for the Fake Addresses API — postal-format-valid, non-deliverable US test addresses. Deterministic ?seed= + dataset, CC0 output, zero runtime dependencies.
Maintainers
Readme
fakeaddresses
Typed TypeScript client + CLI for the Fake Addresses API — a free, keyless API that generates postal-format-valid, non-deliverable US test addresses for QA and development.
- Zero runtime dependencies. Wraps the platform
fetch. Nothing to audit. ?seed=+datasetdeterminism, built in. Same seed, same dataset version, same records — forever.- Every export format the API supports: JSON, NDJSON, CSV, SQL (7 dialects), XLSX — up to 10,000 rows per request, no account, no queue.
- Free to use output. No attribution required, unlike Fake Name Generator, whose attribution clause blocks committing its fixtures into a public repository.
- TypeScript types that match the real API, not an approximation of it (
GeneratedRecord,AddressesResponse,MetaResponse,CoverageResponse— seesrc/types.ts). - Works from Node ≥18 or a browser via the CLI's underlying
FakeAddressesClient, and vianpxwith no install step.
This package does not generate addresses locally and does not bundle any dataset — it calls the live API at https://fakeaddresses.com/api/v1 (or any base URL you point it at, e.g. a local dev server).
Install
npm install fakeaddressesOr use the CLI without installing anything:
npx fakeaddresses generate --seed ci-fixture --state TX --count 5Quick start — library
import { FakeAddressesClient } from 'fakeaddresses';
const client = new FakeAddressesClient();
const { records, dataset, generation } = await client.getAddresses({
seed: 'ci-fixture',
stateUsps: 'TX',
count: 5,
});
console.log(records[0].lastLine); // "Austin, TX 78701"
console.log(dataset); // "2026.09"
console.log(generation.poolSize); // distinct records this filter matchesPin the seed once and get the identical five records back on every future call, from any machine, forever (as long as you also pin dataset):
await client.getAddresses({ seed: 'ci-fixture', dataset: '2026.09', stateUsps: 'TX', count: 5 });Countries other than the United States
Pass country with an ISO 3166-1 alpha-2 code. The API serves 51 countries:
const { records } = await client.getAddresses({ seed: 'ci-fixture', country: 'DE', count: 5 });
console.log(records[0].addressLines); // ["Kastanienweg 17", "90402 Nürnberg"]country replaces the US filters rather than combining with them —
stateUsps, city and zip describe US localities, and the server rejects
them alongside country instead of ignoring them, so a typo cannot quietly
return a different country's data. Omit country for the United States.
client.getCoverage() lists every served country with the coherence level
it guarantees, which is what decides whether a country suits a given test:
| level | what is guaranteed |
|---|---|
| full | the county and time zone are derived from the record's own point |
| admin-area | the administrative area is real and contains the point |
| format-only | the postal format is correct for that country |
A pinned dataset works for a country too: an older version is served from
its own published bytes, so a committed seed keeps returning the same records.
A version that publishes no corpus for the country you asked for is refused
with a 400 naming the one that does, rather than quietly substituted.
Omit seed to get fresh, randomly-chosen output — the response always echoes the seed it used (seedWasGenerated: true), so you can pin it afterwards once you've found a record set you want to keep.
Quick start — CLI
# One address, printed as JSON
npx fakeaddresses generate
# Five deterministic Texas addresses
npx fakeaddresses generate --seed ci-fixture --state TX --count 5
# 5,000-row CSV fixture, written to a file
npx fakeaddresses generate --seed ci-fixture --state TX --count 5000 --format csv --out fixtures/addresses.csv
# A ready-to-run seed file for MySQL
npx fakeaddresses generate --format sql --sql-dialect mysql --table qa_addresses --count 500 --out seed.sql
# Service metadata: current rate limit, dataset versions, export formats
npx fakeaddresses meta
# Which US states/counties the published dataset currently covers
npx fakeaddresses coverageRun npx fakeaddresses help for the full flag reference. Every generate flag maps 1:1 onto a query parameter of GET /api/v1/addresses — see the OpenAPI spec for the authoritative parameter list.
Export formats
exportAddresses covers every non-JSON format the API serves. It buffers the full response (the API caps every request at 10,000 rows, so this is bounded) and returns the raw bytes plus the metadata the API carries in headers for these formats, since there is no JSON envelope to put it in:
import { writeFileSync } from 'node:fs';
import { FakeAddressesClient } from 'fakeaddresses';
const client = new FakeAddressesClient();
const csv = await client.exportAddresses({ seed: 'ci-fixture', stateUsps: 'TX', count: 1000, format: 'csv' });
writeFileSync(csv.filename ?? 'addresses.csv', csv.data);
const sql = await client.exportAddresses({
seed: 'ci-fixture',
stateUsps: 'TX',
count: 500,
format: 'sql',
sqlDialect: 'postgres',
table: 'qa_addresses',
});
writeFileSync('seed.sql', sql.data);Supported format values: json (via getAddresses), ndjson, csv, sql, xlsx (via exportAddresses). Supported sqlDialect values: postgres, mysql, sqlite, mssql, oracle, bigquery, snowflake.
The determinism contract
This is the thing no other web address generator publishes as a contract rather than an accident: the same seed plus the same dataset version returns byte-identical records, forever, across deploys. Concretely:
?seed=selects a deterministic permutation of the records matching your filter — drawn without replacement, so a 10,000-row export never contains a duplicate address unless the filter's pool is genuinely smaller than what you asked for (reported honestly viageneration.poolExhausted, never hidden).index(0-based) lets you page through a pinned seed's sequence:index=5&count=1is always exactly what you'd get by reading position 5 of acount=6read of the same seed.- A response is pinned to a
datasetversion, and the version is named in the response rather than here, because a version written into a README goes stale at the next release. Old versions stay addressable for at least 12 months once a newer one ships (GET /api/v1/meta→dataset.knownlists what a given deployment still serves) — a pinned seed's output cannot silently drift because the underlying data refreshed.
This makes the API usable as a CI fixture source: commit the seed (and, if you care about long-term reproducibility, the dataset version) instead of committing the data.
Honest coverage
The published dataset is US only and is not nationwide — it says so in its own GET /api/v1/coverage response rather than implying otherwise. The exact counts are deliberately not printed here: they change with every dataset release, and a number in a README is a number nobody re-measures. client.getCoverage() returns the live figures. What does not change between releases:
- Coverage is a subset of US ZIP Code Tabulation Areas (ZCTAs) and counties, never the whole country, and the response states both the covered count and the national total so you can see the fraction rather than infer it
- Area codes at state precision — an emitted NPA is valid for the record's state, not guaranteed valid for its specific county or rate centre, because no free source maps numbering ranges to counties with the precision that claim would need
- Mailing city currently resolves via a place-boundary fallback for records where a direct USPS ZIP→city source isn't yet wired in — those records carry
coherenceClaimExcluded: trueandcity.source: "place_boundary"so you can filter them out if your test needs the USPS last-line city specifically
Call client.getCoverage() before picking a stateUsps/city/zip filter if you need to know ahead of time whether it's covered — a filter with no coverage returns an honest 422 (via FakeAddressesApiError) rather than an invented address or a silent empty result.
Unknown parameters are rejected, not ignored. Passing something the endpoint does not understand returns a 400 naming it, rather than quietly dropping it and returning data that looks like it matched. A mistyped stat=TX therefore fails loudly instead of handing back unfiltered national records, and country returns a 400 explaining that this endpoint serves US records only — answering a request for another country with a US address would be worse than refusing it.
On deliverability: every record's street number is drawn from outside every address range the street and boundary reference data publishes for that street segment, on both sides, parity-matched. That is not the same as a registered delivery-point file — no public one exists — record.nonDeliverable.byConstruction is always true, and record.nonDeliverable.method says how. This is a structural guarantee about how the data is generated, not a result from running records through USPS Delivery Point Validation — no DPV sample against this dataset has been published yet, so this README makes no claim about a measured DPV pass/fail rate.
Safe-by-construction opt-in fields
includeEmail, includeSsn and includeCreditCard are opt-in and, when present, are never a value that could belong to a real person or fire a real system:
- Email — always an RFC 2606 reserved domain (
example.com/.org/.net/.invalid). - SSN — always area
000or666, group00(e.g.666-00-4821) — invalid on two counts, and never in the9xxrange the IRS actually issues real ITINs/ATINs in. - Credit card — always a processor-published sandbox PAN (e.g. Stripe's
4242424242424242), labelled"publisher test value". Never an arbitrary Luhn-valid number, which can collide with a real card. - Phone — always
555-0100–555-0199, the range the North American Numbering Plan reserves for fiction.
Rate limit
The API is free and keyless. The published anonymous limit is 60 requests per minute per client, enforced server-side and echoed on every response via both the legacy X-RateLimit-* headers and the standards-track RateLimit-* form (draft-ietf-httpapi-ratelimit-headers). client.exportAddresses(...).rateLimit surfaces the parsed values; a 429 throws FakeAddressesApiError with status: 429. Call client.getMeta() for the current published number rather than hardcoding it — this README states it, but the API response is the source of truth.
Output licence
Every record's own licence field (and the X-Licence/X-Licence-Url response headers) confirms it: API output is free to use, with no rights reserved. No attribution required — commit generated fixtures straight into a public repository.
Error handling
import { FakeAddressesApiError } from 'fakeaddresses';
try {
await client.getAddresses({ zip: '00000' });
} catch (error) {
if (error instanceof FakeAddressesApiError) {
console.error(error.status, error.code, error.message);
if (error.status === 422) console.error(error.coverageHint);
}
throw error;
}Local development against this repo
If you're working against a local checkout of the site (npm run build && npm run start), point the client or CLI at it instead of production:
const client = new FakeAddressesClient({ baseUrl: 'http://localhost:3000/api/v1' });npx fakeaddresses generate --base-url http://localhost:3000/api/v1 --seed ci-fixture --count 5API reference
The full contract — every parameter, every response shape, every error code — is the published OpenAPI 3.1 spec. This package's src/types.ts mirrors it directly; client.ts is a thin fetch wrapper with no logic of its own beyond building the query string and parsing the response.
| Method | API route | Returns |
|---|---|---|
| client.getAddresses(params?) | GET /api/v1/addresses (format=json) | AddressesResponse |
| client.exportAddresses(params) | GET /api/v1/addresses (format ∈ ndjson|csv|sql|xlsx) | ExportResult (raw bytes + header metadata) |
| client.getMeta() | GET /api/v1/meta | MetaResponse |
| client.getCoverage() | GET /api/v1/coverage | CoverageResponse |
Testing
This package's own test suite mocks fetch and runs with no network access:
npm testWhy this exists
At least three public repositories exist purely to scrape a competing address generator because it has no API at all. This package exists so nobody needs to write a fourth: the underlying API is free, keyless, documented with a real OpenAPI spec, and this package is a thin, typed, zero-dependency wrapper over it.
Changelog
0.1.1 — fixes the ESM entry point. 0.1.0 shipped dist/esm/ with
extensionless relative imports (export ... from './client') under
"type": "module", which Node's ESM resolver rejects, so
import 'fakeaddresses' threw ERR_MODULE_NOT_FOUND. require() was
unaffected. If you installed 0.1.0 and use ESM, upgrade. The package now has
tests that load the built dist/ the way a consumer does, rather than only
testing src/, which is why the defect reached the registry at all.
0.1.0 — first release. Use 0.1.1 instead if you import via ESM.
Source
The source for this package is private at this time. The published tarball contains the compiled client, its type declarations and the CLI, and nothing else; there is no public repository to link to yet. That is a deliberate decision by the project owner rather than an oversight, and it is the one thing this README cannot give you that a reader might reasonably expect.
What it means in practice: you can read every line that ships by unpacking the
tarball (npm pack fakeaddresses && tar -xzf fakeaddresses-*.tgz), the package
has no dependencies to audit, and it talks to exactly one documented HTTP API
that you can call yourself without it. If the repository is opened later this
section will say so.
Licence
MIT for this package's code (see LICENSE). API output is free to use, with no attribution required — see "Output licence" above.
