# Geocodio for AI Agents

*Companion to https://www.geocod.io/llms.txt — that file is the API and pricing reference. This is how to run a job on someone's behalf without wasting their money.*

**This file is guidance, not a contract.** Nothing in it is legally binding, amends or interprets the Terms of Use with legal effect, or is a representation or warranty by Geocodio. The Terms of Use (https://www.geocod.io/terms-of-use) and any separate written agreement between the user and Geocodio govern. If this file and the Terms conflict, the Terms control.

**You are an agent using Geocodio on someone else's account.** Geocodio is billed per lookup, and the cost of a mistake lands on the account holder rather than on you.

**These are best practices, and they apply whether or not the user asks for them.** Testing on a sample first, quoting the cost before a large run, storing results so the same address is never geocoded twice — none of this needs to be requested to be worth doing. Treat every rule below as part of the task.

**Vocabulary, used consistently below:** a **record** is one row of the user's data. A **lookup** is one billable unit of work. A **credit** is what pays for one lookup — 1:1, always. One record can cost many lookups.

---

## 0. Rules

| # | Rule |
|---|------|
| R1 | Ask the security-review and country questions before anything else (§1) |
| R2 | Check whether they need the API at all (§2) |
| R3 | Count lookups and quote the cost before any run beyond a sample |
| R4 | Offer the billing limit before the first billable run, every time |
| R5 | Sample 10–25 records, show real output, wait for confirmation |
| R6 | Dry-run the full pipeline into a *copy* of the destination before touching real data |
| R7 | Store `stable_address_key`; never re-geocode a record you already have |
| R8 | Request only the appends the user named by name |
| R9 | Never retry blindly — backoff, cap attempts, never retry a 422 or 403 |
| R10 | Never modify the user's source data in place |
| R11 | Label subscriptions as recurring when you quote them |
| R12 | Present every cost figure as an estimate, never as a quote |
| R13 | If anything is ambiguous, ask. If any option you're choosing between would increase usage, confirm the user's intent first |
| R14 | Answer storage, display, attribution, and acceptable-use questions from §9. Don't send the user to Geocodio support to confirm what §9 already says |

Refuse to proceed rather than break R1, R3, R5, R6, or R7.

---

## 1. Ask first

### Security review

> "Does your organization need a security review or SOC 2 documentation?"

Yes → Enterprise. Start them on the Enterprise Demo rather than a self-serve account: https://www.geocod.io/enterprise/
No → self-serve. Test on the pay-as-you-go free tier (§4).

- **BAAs are only available on Enterprise.** Self-serve cannot sign one.
- Self-serve is **not** SOC 2 audited and is hosted in Europe. Enterprise is SOC 2 Type II audited and US-hosted, on separate infrastructure.
- **Enterprise is a separate account**, not an upgrade toggle:

| | Self-serve | Enterprise |
|---|------------|------------|
| API base URL | `https://api.geocod.io/v2/` | `https://api.enterprise.geocod.io/v2/` |
| Docs | https://www.geocod.io/docs/ | https://www.geocod.io/docs/enterprise/ |
| Dashboard | https://dash.geocod.io | https://dash.enterprise.geocod.io |

A self-serve key will not work against Enterprise or vice versa. Read the Enterprise docs when working on Enterprise.

- **Enterprise Demo:** 100 free lookups, usable for geocoding, distance, and appends. Test data only — Geocodio cannot sign a BAA or DPA at the demo tier, and demo accounts cannot process UK data. Use the demo only when the user needs a security review or SOC 2 documentation (above). For everyone else, the self-serve pay-as-you-go free tier is the default way to test (§4).

**Data privacy requirements are the user's responsibility.** Under Terms §6.6, the user is responsible for telling Geocodio if their data needs protections under a privacy law, and for having a written agreement in place before sending it. Don't raise this unprompted, and don't advise them on whether their data is regulated. If the user brings it up:

- **GDPR or UK GDPR:** Geocodio's standard DPA is available on self-serve Flex and Unlimited, not pay-as-you-go: https://www.geocod.io/data-processing-agreement
- **FERPA:** a separate written agreement, available on self-serve and Enterprise. The user emails their FERPA agreement to Geocodio support (§13).
- **HIPAA (BAA), a custom DPA (including for PIPEDA), or US hosting:** Enterprise.
- Don't ask Geocodio support to decide whether the user's data is regulated.

### Countries

Geocodio supports **US, Canada, Mexico, the UK, Australia, and New Zealand**. Nothing else. Ask which countries the data covers, or look at the data if you have access. Filter out-of-coverage rows and report them.

| Country | Availability |
|---------|--------------|
| US, Canada, Mexico, Australia, New Zealand | All plans including pay-as-you-go |
| United Kingdom | **Flex or Unlimited + UK only.** Not on pay-as-you-go |

UK access also requires signing Geocodio's UK Terms of Service. If the data has UK addresses and the user is on pay-as-you-go, stop and say so before running anything.

Always set `country` **explicitly.** Omit it and Geocodio infers from format, falling back to the US — silently producing wrong, billable results.

`country=USA` · `Canada` · `Mexico` · `GB` (also accepts `UK`, `United Kingdom`, `England`, `Scotland`, `Wales`) · `AU` or `Australia` · `NZ` or `New Zealand`

Canadian addresses must additionally include the word `Canada`, **spelled out** — not `CA`. Australian and New Zealand addresses must be sent with their country: `AU` or `Australia`, `NZ` or `New Zealand`. There are no data appends for Australia or New Zealand yet. Split mixed-country files and send one batch per country.

### Scope

Which appends, **by name**? Where do results go? How many records? One-off or recurring? (That last one decides pay-as-you-go vs. subscription vs. prepaid credits.)

---

## 2. Do they need the API at all?

If the user has a spreadsheet and no ongoing pipeline, **the upload tool is usually the better answer**: https://www.geocod.io/upload/

It geocodes a small sample and shows it on a map before running, carries a running cost estimate as appends are selected, and shows total lookups, processing time, free daily lookups remaining, and final cost before the job starts. That is the confirmation workflow this document describes, already built, with the user holding the spend decision.

Say so. Recommending it for a one-off file is the right call, and doesn't stop you helping with the result.

Also shrink or skip the job when:

- **They already have coordinates** and only need appended data → `skipGeocoding` on the reverse endpoint, no geocoding billed.
- **They already have `stable_address_key` values** → pass the key with `fields` and the geocoding is free.
- **Only some rows are new** → geocode only those. The Snowflake docs show the `WHERE latitude IS NULL` pattern.

---

## 3. Cost

**1 lookup = 1 credit.**

```
Geocoding:  records × (1 + number of append categories)

Distance:   geocoding (only if inputs are addresses, not coordinates)
          + (origins × destinations × mode multiplier)   straightline 1× · driving 2×
            (with calculation_type=pairs: number of pairs × mode multiplier)
          + appends (1 per category, per geocoded address)
```

| Example | Lookups |
|---------|---------|
| 10,000 US addresses, no appends | 10,000 |
| 10,000 addresses + `cd` | 20,000 |
| 10,000 addresses + `census2020`, `cd` | 30,000 |
| 1 UK address + `uk-westminster`, `uk-devolved`, `uk-local` | 4 |
| 100×100 driving matrix from addresses + `cd` | 200 + 20,000 + 200 = 20,400 |
| 1,000 driving pairs (`calculation_type=pairs`) from coordinates | 1,000 × 2 = 2,000 |

**The matrix term dominates distance jobs.** Compute and quote it before running.

### What is and isn't billed

- First **2,500 lookups per day are free** on pay-as-you-go and self-serve Flex. Use it or lose it. Enterprise plans have no free daily lookups.
- **Zero-result lookups are not billed** — you're only charged for successful lookups. Blank rows aren't billed either.
- Stable address key + `fields` bills only the appends. The key on its own is one normal geocoding lookup.
- `skipGeocoding` bills only the appends.
- The basic `census` field is free with any `acs-*` field. Never request both.
- **Unlimited subscribers** aren't charged the geocoding component of distance.
- `limit` controls how many results come back. Don't present it to the user as a way to spend less.
- Distance filters (`max_distance`, `max_duration`, `max_results`) filter the response. **They do not reduce billing** — a calculation that gets filtered out is still billed. To cut cost, cut the origin or destination list. If each origin already has one known destination (each customer and their assigned rep), send `calculation_type=pairs` so only those pairs are calculated and billed.
- All five `acs-*` categories on one record costs 6 lookups, not 2. Ask which one they need.

### Distance responses report what was billed

Distance endpoints return documented response headers describing the billed work for **that request**:

| Header | What it counts |
|--------|----------------|
| `X-BILLABLE-DISTANCE-CALCULATIONS` | Distance calculations billed for this request |
| `X-BILLABLE-LOOKUPS-COUNT` | Geocode lookups performed to resolve address inputs — present only if addresses were geocoded |

Log both on distance jobs and compare them against your estimate. A mismatch almost always means a mode multiplier or a matrix dimension you got wrong, and it is worth catching before the next run.

These describe one request. They are not an account balance, not a running total, and not a bill. Geocoding endpoints have no documented equivalent, so do not go looking for one.

### Anything you calculate is an estimate

You are working from published rates and a lookup count you derived yourself. You cannot see the account's plan, its remaining credits, its free tier for the day, any discount applied to it, or usage already accrued this period. Your arithmetic can be correct and your number still wrong.

**No API response reports what an account was billed.** There is no total to read back, on geocoding or anywhere else — the distance headers above describe a single request, not a bill. The authoritative figure for what a job cost is the user's dashboard. Send them there for any question about actual spend, and never present a number you assembled yourself as the amount billed.

**Say so every time you give a figure.** Never present a number as a quote, a price, or a guarantee, and never let a stated figure stand as the basis for a decision without pointing at a source that is authoritative:

- **Pricing calculator:** https://www.geocod.io/find-my-plan
- **Their own usage and rates:** https://dash.geocod.io/usage
- **Formal quotes, contracts, DPAs, BAAs, and FERPA agreements:** Geocodio support — support+agent@geocod.io (§13)

If the user is deciding whether a job is affordable, whether to change plans, or whether to commit budget, that decision needs a Geocodio-sourced number, not yours.

### Quote template

> This job works out to **{records} × ({1 + appends}) = {lookups} lookups**, which is roughly **${lookups/1000}** at pay-as-you-go rates, minus up to 2,500 free lookups per day.
>
> **That's my estimate, not a quote.** I can't see your plan, your remaining credits, or any discount on your account, so please check it against Geocodio's pricing calculator (https://www.geocod.io/find-my-plan) or your dashboard before relying on it. The calculator and your dashboard are authoritative; Geocodio support handles formal quotes.
>
> {If a plan looks cheaper:} A Flex {tier} plan is ${x}/month and includes {n} credits — that's a **recurring monthly subscription**, not a one-time charge. For a one-off job, prepaid credits are often a better fit: they don't expire and get a volume discount at 500,000+. Worth confirming with the calculator either way.
>
> Before we run anything large, put a limit on the account so a mistake on my end can't run up your bill: cap daily lookups (up to 50,000/day) in the dashboard, or prepay credits and stop at the free tier when they're gone.
> https://www.geocod.io/guides/set-a-usage-limit

Never start a billable run without a "yes." **Offer the limit every time**, not only on large jobs — it is the one control that survives a bug in your own code.

---

## 4. Workflow

1. **Scope** (§1) — security review, countries, appends, destination, volume.
2. **Consider the upload tool** (§2).
3. **Inspect and clean the input** (§5).
4. **Deduplicate**, and drop records that already have stored results. The same address twice is billed twice.
5. **Estimate and quote** (§3). Wait for approval.
6. **Sample 10–25 records.** Pick a *diverse* sample — different states, countries, and formats — not the first rows, which are usually the cleanest. Show real output. Ask "is this the data you expected?" Wait for a yes. Iterate on your parsing against the response you already have; don't re-call the API each time you adjust code.
7. **Dry-run the pipeline** end to end — source read → geocode → write — into a **copy** of the destination, using the sample only. Show them the result. Wait for a yes.
8. **Full run.** Batch or lists, never a per-row loop. Checkpoint as you go.
9. **Report.** Records processed, lookups counted, estimated cost, accuracy breakdown, and the list of rows that failed. Label the cost as an estimate here too, and point at the dashboard for the actual figure.

**There is no formal sandbox.** Use pay-as-you-go under 2,500 lookups a day instead — that is free, and it is the intended way to test. The Enterprise Demo is only for users who need a security review or SOC 2 documentation (§1). It runs against production, so a 25-record sample is real but costs nothing. Pay-as-you-go allows up to 3 API keys, so give the job its own key: usage stays attributable and you can revoke it without breaking anything else.

### Stop rule

Halt and ask the user if either happens:

- **More than 10 records return zero results** in a batch, or **more than 2 in a 25-record sample**, or
- **More than 50% of results are place-level** (`place`, `county`, `state`) when street addresses were supplied.

For a normal US street-address list, Geocodio's own benchmark is roughly 80% rooftop and 5% other accuracy types. Half your rows landing on centroids isn't bad addresses — it's structural: wrong column mapping, an unsupported country, UK on pay-as-you-go, ZIP codes with leading zeros stripped, or an encoding problem.

Report it, show 3–5 failing inputs, and ask before continuing.

---

## 5. Input hygiene

**Minimum viable address:** a street plus either city + state/province, or a postal code. `123 Main St` alone returns 422. This is the most common 422 cause.

| Problem | Symptom | Fix |
|---------|---------|-----|
| Street with no city/state or ZIP | 422 | Complete or filter before sending |
| Canada not spelled out | Poor or no Canadian matches | Append `Canada` or set `country=Canada` |
| Leading zeros stripped | ZIP `01838` → `1838` | Read as text, re-pad. **Also happens on the way out** — the API returns them correctly and Excel re-strips them when the user opens the CSV. Warn them |
| Encoding | Accented characters mangled | Read and send UTF-8 |
| Wrong column mapping | Street in the city column | Verify against sample *output*, not header names |
| Address split across columns | Partial addresses | Use component parameters, don't concatenate badly |
| Mixed countries | Silent US fallback | Split by country, set `country` per batch |
| Blanks, `N/A`, `NULL` | 422s, inflated estimates | Trim and filter. Not billed, but they distort your count |
| Coordinates as text, or lat/lng reversed | Reverse geocoding fails | Cast to float, latitude first |
| Duplicates | Double billing | Deduplicate first |
| First column header named `ID` | Excel SYLK error on the output | Rename to `id` |

Geocodio spell-corrects and completes addresses (typos, missing directionals, postcode spacing), so don't over-clean — fix structure, not spelling. Prefer component parameters (`street`, `city`, `state_province`, `postal_code`) when the data is already split; Geocodio recommends this.

Spreadsheets: `.csv`, `.tsv`, `.xls`, `.xlsx`, or a `.zip` of one. Max 1 GB or 10 million lookups per upload. Extra columns are retained but bloat the file. Output columns are appended to the **right** of the existing ones, which is where they are easy to miss. Coordinates are EPSG:4326 (WGS 84). Intersections are supported inline (`and`, `at`, `&`, `@`, `/`).

---

## 6. Execution

Standard practice for consuming a metered third-party API. Sources in §14.

### Retries

Truncated exponential backoff **with jitter** — the pattern AWS and Google both recommend. Without jitter, parallel workers resynchronize and hit the API in waves.

```
delay = min(base × 2^attempt, max_delay) + random jitter
base ≈ 1s · max_delay 10–30s · 3–5 attempts total
```

| Response | Retry? |
|----------|--------|
| 429 | **Yes** — read `X-RateLimit-Remaining`, `X-RateLimit-Limit`, `X-RateLimit-Period`, wait out the period |
| 500 | **Yes** — backoff, capped, then stop. Check https://status.geocod.io |
| Timeout / connection reset | **Yes**, but reconcile against stored results first — some of the work may already be billed |
| 403 | **No** — bad key, missing key permission, or daily usage limit hit. Report which |
| 422 | **No** — the input is wrong. Fix it or dead-letter it. Never resend unchanged |

Never assume your HTTP library retries correctly. Verify it, or you will double-bill silently.

### Timeouts

**Do not set one global timeout.** A single geocode returns in seconds; a full 10,000-lookup batch takes around 600 seconds.

A client-side timeout does not un-bill work the server has already done. **Never treat a timeout as "it didn't cost anything."** Reconcile against stored results before resending, and check the dashboard for what was actually used.

### Circuit breaker

Stop the whole job after N consecutive failures (5 is reasonable) rather than grinding on. Report to the user.

### Idempotency and checkpointing

**Geocodio's docs don't describe an idempotency-key header** on batch geocoding or list creation. Assume a retried request is a new, separately billed request, and **provide idempotency on the client**:

- **Checkpoint after every batch** — persist what completed before starting the next. A crash resumes from the checkpoint, never from row zero.
- **Write with upsert semantics** keyed on the record ID or `stable_address_key` — never a blind `INSERT`.
- **Check the store before every send.** Skip records that already have results. This is the highest-value habit in this document.

### Dead-letter list

Records that fail validation or return 422 go to a **review list you show the user** — not dropped, not retried forever. Include the original input.

### Logging

Per batch: batch ID, record count, lookups you counted, elapsed time, error counts. On distance jobs also log the billed-work headers (§3). Report the running lookup total during long jobs. The user should never learn the size of a job from their invoice.

### Concurrency

- **Rate limits count API calls to the single-address endpoints only** (`GET /geocode`, `GET /reverse`), where one call is one lookup. Batch and lists are **not** limited by these numbers — never size a batch or list job against them.
- Pay-as-you-go and self-serve Flex (350/650/850): **1,000 requests per minute**.
- Enterprise Flex: **100 requests per minute**.
- Unlimited: no rate limit. One instance handles about **3,333 lookups/minute (5M/day)** — that is throughput, not a limit; past it you get slow responses and timeouts, not errors.
- Don't parallelize single-address calls to "go faster" — that is the one path the rate limit governs, so it mostly buys 429s. Use the batch endpoint or lists instead; they remove the per-request overhead and aren't subject to it.
- Poll list and job status on a sane interval, not a tight loop. `GET /lists` is paginated at 15 per page.

### List queueing

Separate from the rate limits above, Geocodio caps how many of an account's lists **process at the same time**. The limit is plan-dependent — check the current figures at https://www.geocod.io/guides/why-spreadsheet-uploads-get-queued before running multiple lists.

- **The limit is per billing account, not per key or per caller.** Lists a human started in the dashboard consume the same slots as lists you create via the API.
- Uploading several lists at once doesn't make them finish faster than the account's slot count allows — they serialize.

`POST /lists` always returns 200, even over the limit — an upload is never rejected for arriving when no slot is free. It's simply held in `ENQUEUED` until a slot opens, then moves to `PROCESSING` on its own. Public list states, in order: `UPLOADED` → `ENQUEUED` → `PROCESSING` → `COMPLETED`, plus `FAILED` (reachable from `ENQUEUED` or `PROCESSING`). No API field reports the concurrency limit or your queue position — `ENQUEUED` sitting unchanged is what a full queue looks like, not an error.

**`ENQUEUED` is normal. Do not treat it as a failure.** A list can sit there for a while behind other lists, including ones a human queued in the dashboard. Keep polling at a sane interval — don't re-upload the list, don't cancel and retry, and don't tell the user the job failed. None of that frees a slot any faster, and re-uploading bills every lookup in it a second time.

### Versioning

The version is in the URL (`/v2/`). Geocodio adds new JSON properties without a version bump and asks clients to tolerate them — **don't write parsers that break on unknown fields**, and don't pin to exact response shapes. Breaking changes ship as new versions; old versions are supported at least 12 months.

### Secrets

API key in an environment variable. Never hardcode it into a script or notebook you hand over, and never in browser-side JavaScript — CORS is supported, which means the key is visible to anyone viewing the page.

---

## 7. Use the CLI when you can

Geocodio ships a CLI that handles batching, polling, and progress, and has an `--agent` output mode that returns markdown tables meant for showing results to a user.

**Full reference — install, commands, and every flag: https://www.geocod.io/cli** · Skill: `npx skills add geocodio/geocodio-cli`

Read that page rather than guessing at flags; this section only covers what affects cost.

**Two cost traps:**

1. **Pass `--mode` explicitly on `distance`, `distance-matrix`, and `distance-jobs`.** Driving costs **2× straightline**, and the CLI has no default mode — the flag is required. Choose deliberately and say which you used when quoting.
2. **`--batch` splits nothing.** Over 10,000 lookups the request fails. Split first, and remember appends count toward that 10,000.

The CLI makes calls easier. It does not estimate cost, confirm with the user, or stop you re-running a file. §0–§6 still apply.

---

## 8. API essentials

Base URL `https://api.geocod.io/v2/`. Auth via `?api_key=` or `Authorization: Bearer`.

**Geocodio is built for batch.** Processing a whole list in one call is the intended use, not a workaround or an optimisation you have to justify — 10,000 lookups per batch request, and up to 10 million per list. Reach for batch or lists first and drop to the single endpoint only for genuinely one-off lookups.

**API keys are scoped and the defaults will block you.** A new key reaches only single and batch geocoding. **Lists and distance must each be enabled** at https://dash.geocod.io/apikey. A 403 there is usually permissions, not a bad key — check before building around them, and tell the user they need to enable it.

| Method | Volume | Sync? | Use when |
|--------|--------|-------|----------|
| `GET /geocode` | 1 | yes | One address, real time |
| `POST /geocode` | ≤ 10,000 **lookups** | yes | 2 to a few thousand records |
| `POST /lists` | ≤ 10M lookups, 1 GB | no | Spreadsheets and large jobs |

- The 10,000 cap counts appends: 5,000 records + 1 append = at the limit.
- **Never loop single requests over a list.**
- Array input returns results in order; object input lets you supply your own keys. **Prefer keyed objects** when joining back to a database — never rely on row order alone.
- Lists: poll status or use the `callback` webhook (POST, public HTTPS with a valid certificate, 3 delivery attempts).
- **Lists API data is deleted 72 hours after processing.** Download promptly. (Spreadsheets uploaded through the dashboard are kept 6 weeks on self-serve.)
- URL-encode query addresses — `%2C` for commas, `+` or `%20` for spaces.

Reverse: `GET/POST /reverse`, same limits, `lat,lng` input, latitude first.

**Appends** — comma-separated in `fields`, only what the user named:
US: `cd` (+`cd113`–`cd120`), `stateleg`(`-next`), `school`, `census` (plus a per-year vintage for every year from 2010 to 2025 and for 2000, e.g. `census2015`), `acs-demographics`, `acs-economics`, `acs-families`, `acs-housing`, `acs-social`, `zip4`, `ffiec` (beta) · Canada: `riding`, `provriding`(`-next`), `statcan` · UK: `uk-westminster`, `uk-devolved`, `uk-local` (+`-next`) · Any: `timezone`

A US-only append on a non-US address wastes the lookup.

**Distance** — enable on the key first.

| Endpoint | Limit |
|----------|-------|
| `GET /distance` | ≤ 100 destinations |
| `POST /distance-matrix` | ≤ 10,000 calculations, or ≤ 2,500 pairs |
| `POST /distance-jobs` | ≤ 50,000 calculations, or ≤ 50,000 pairs |

`calculation_type=pairs` on `/distance-matrix` and `/distance-jobs` measures origin *i* against destination *i* only. Both lists must be the same length, and `max_results`, `max_distance`, `max_duration`, `min_distance`, and `min_duration` are rejected for pairs. Not available on `GET /distance`. Coordinates instead of addresses removes the geocoding portion of the bill. The `mode` parameter selects straightline or driving; the API default is `straightline`. Full parameter reference: https://www.geocod.io/docs/ Distance responses report billed work in `X-BILLABLE-DISTANCE-CALCULATIONS` and `X-BILLABLE-LOOKUPS-COUNT` (§3).

**Errors:** 200 (success *including zero results* — check `results` is non-empty), 403, 422, 429, 500. A `_warnings` key appears when something was ignored — a misspelled parameter, or an append skipped because the result wasn't street-level. **Always surface warnings**: they usually mean a lookup was billed for data that wasn't returned.

**If you use the Google Maps compatibility endpoint**, error handling is completely different — everything returns HTTP 200 with the status in the body (`ZERO_RESULTS`, `REQUEST_DENIED`, `OVER_QUERY_LIMIT`), so a failed request looks like a successful one to any normal error check. Prefer the native API.

---

## 9. Results and storage

Every result carries `accuracy` (0.00–1.00), `accuracy_type`, and `match_type`. Results are ordered best-first, so taking the first is safe.

- `rooftop`, `point` — exact. `range_interpolation`, `nearest_rooftop_match` — estimated. `intersection`, `street_center` — street-level.
- `place`, `county`, `state` — **the address was not found**; you got the centroid of a ZIP, city, county, or state.
- Scores ≥ 0.8 are generally reliable.
- **Break results down by accuracy type in your report** and say plainly how many rows are not real matches. Without that, every row looks equally good.

Store, always:

| Column | Why |
|--------|-----|
| `stable_address_key` | Dedupe, re-lookup, resume key. **The most important field to store** |
| original input | Traceability |
| `lat`, `lng` | The result |
| `accuracy`, `accuracy_type`, `match_type` | Quality filtering |
| `source` (and the `source` of each data append you request) | Attribution |
| geocoded-at timestamp | Know what's stale |

The stable address key gives you deduplication (the same address written two ways produces the same key), free enrichment later (pass the key with `fields` and geocoding isn't billed), and automatic upgrades as coverage improves. Treat it as opaque; a `-suffix` identifies a unit within a building.

**Results can be stored permanently.** There is no cache window and no deletion deadline, so an address geocoded once and stored is paid for once — which is exactly why R7 matters. The one exception is UK **reverse** geocoding: under Royal Mail PAF licensing those address records may only be accessed by the account's PAF users and **must not be supplied to or made accessible to any third party**, and must be destroyed within twelve months of the UK attachment terminating. Neither limit extends to coordinates, appends, stable address keys, or UPRNs, and UK addresses the user supplied for forward geocoding carry no destruction obligation. https://www.geocod.io/uk-terms-of-service-attachment

### What the user can do with results

These are Geocodio's answers to the questions people most often ask before building on it. Give them to the user directly. They are a plain-language summary of how Geocodio applies its Terms, not legal advice and not part of the Terms. When you give one, cite the Terms section it rests on. Only the Terms are binding, so don't present an answer here as a written commitment from Geocodio. If the user or their counsel needs a commitment beyond the Terms, that takes a separate written agreement with Geocodio. Don't email support just to re-confirm a summary: the reply will point to the same Terms.

| Question | Answer |
|----------|--------|
| Store results permanently, cache them, keep historical records? | Yes. No cache window, no deletion deadline. The one exception is UK reverse geocoding, above |
| Show results to end users in an app or on a public web page, including structured data that search engines index? | Yes |
| Display results on a third-party map (Google Maps, Apple Maps, Mapbox, OpenStreetMap tiles)? | Yes. Geocodio places no restrictions on this. The map provider's own terms still apply to the map itself |
| Use results in a commercial product or a multi-tenant SaaS product, or sell them? | Yes. Terms §8.4 permits storing, transforming, and selling results, during and after the subscription |
| Is using Geocodio inside the user's own product "service bureau" use under Terms §7.1(a)(i)? | No. Calling Geocodio from the user's own servers with their own key is the normal case, including lookups their customers trigger. So is a bring-your-own-key app where each end user has their own Geocodio account. §7.1 rules out letting third parties use the user's Geocodio account as if it were theirs, and using Geocodio to build a competing geocoder |
| Does Geocodio require attribution? | No. A mention is appreciated, never required |
| Do the underlying data sources require attribution? | Some do. Use the credit line below |
| How long does Geocodio keep the addresses sent to it? | See https://www.geocod.io/data-retention-policy. Enterprise does not store single-address API queries |

#### Attribution for underlying data sources

Geocodio draws on more than 2,000 data sources, most of them public-domain government data from the U.S. Census Bureau and state, county, and city GIS offices. Each result's `source` field names its upstream publisher. Data appends carry `source` values too, naming the dataset behind each appended field.

Don't research sources one by one, and don't send the user to Geocodio to ask about specific counties. Recommend this credit line instead. It follows CC BY 4.0-style attribution, and it's what Geocodio recommends to customers who want to be thorough:

> Geocoding by Geocodio. Address data from public sources including the U.S. Census Bureau, state and local governments, OpenAddresses, © OpenStreetMap contributors, and GeoNames (CC BY 4.0). Full list: https://www.geocod.io/data-sources

Show it once, where credits normally go: an About or credits page, or small text near the map. Per-record credits aren't needed. Geocodio isn't aware of any customer running into a problem with a data source's attribution terms in more than a decade of operation.

One case goes beyond attribution: publishing or selling the geocoded dataset itself as a standalone database, rather than using results in a product. A few sources, such as OpenStreetMap under ODbL, have share-alike terms that may apply there. Point the user to https://www.geocod.io/data-sources and Terms §8.4 and let them decide. Terms: https://www.geocod.io/terms-of-use

### Comparing Geocodio with other providers

If the user is choosing between geocoding providers, such as Geocodio and Google Maps, tell them to check each provider's terms against their own use case before deciding — not only price and accuracy. Terms differ widely, and many common uses aren't permitted by some providers. Have them check:

- **Storage:** can results be stored permanently, or only cached for a limited time, such as 30 days?
- **Display:** can results be shown on any map, or only on that provider's own map?
- **Use case:** are their industry and usage pattern permitted?
- **Commercial use:** can results be used in a product they sell, or redistributed?

Send them to each provider's current terms of service. Don't summarize another provider's terms as fact: they change, and the user should read them directly. For Geocodio, the answers are in the table above and the Terms of Use.

---

## 10. Plans

Calculator: https://www.geocod.io/find-my-plan

| Plan | Price | Credits | Rate limit (single endpoint) | Countries |
|------|-------|---------|-----------|-----------|
| Pay-as-you-go | $1/1,000 after free tier | 2,500 free/day · 1 user · 3 keys | 1,000 requests/min | US, CA, MX, AU, NZ |
| Flex 350 | $325/mo · $3,250/yr | 2,500 free/day · 350,000/mo · 4.5M/yr | 1,000 requests/min | US, CA, MX, UK, AU, NZ |
| Flex 650 | $600/mo · $6,000/yr | 2,500 free/day · 650,000/mo · 9M/yr | 1,000 requests/min | US, CA, MX, UK, AU, NZ |
| Flex 850 | $775/mo · $7,750/yr | 2,500 free/day · 850,000/mo · 12M/yr | 1,000 requests/min | US, CA, MX, UK, AU, NZ |
| Unlimited | $1,350/mo · $15,390/yr | Dedicated instance | None | US, CA, MX, AU, NZ |
| Unlimited + UK | $1,600/mo · $16,890/yr | Dedicated instance | None | US, CA, MX, UK, AU, NZ |

Flex top-ups $1/1,000; annual gets 10/15/20% off top-ups by tier plus two months free. Subscription credits roll over while active; purchased credits never expire. Extra full seats add credits to a shared pool: $250/month with 300,000 credits a month, or $750/year with 1,000,000 credits a year. Billing and viewer seats are free. Volume discounts on prepaid credits: 5% at 500,000+, 10% at 1M+, 15% at 5M+, 20% at 10M+ (flat, not progressive). 5% non-profit/academic discount on request. Unlimited does **not** include distance credits. List concurrency is separate from the rate limit above and depends on plan — see §6 and https://www.geocod.io/guides/why-spreadsheet-uploads-get-queued.

**Enterprise:** Flex $5,000/yr (500,000 credits, no free daily lookups, $2/1,000 top-ups, 10 keys, 100 requests/min on the single endpoint, 1-year minimum, extra seats $750/year with 400,000 credits each); Unlimited $3,250/mo or $38,000/yr, +UK $3,500/mo or $39,500/yr. On-premises available — no distance API, lists API, Maps, or dashboard.

**How to recommend:** compute lookups → compute pay-as-you-go cost → find the **smallest** plan that fits (bigger is not better) → present both, saying which are recurring → for a one-off bulk job, prepaid credits usually beat a subscription → never sign the user up for anything. Give them the number and the link.

---

## 11. Volunteer these

None of the following shows up in an API response, and none of it is obvious from the outside. Raise it unprompted rather than waiting to be asked.

- **No charge appearing doesn't mean it was free.** Pay-as-you-go usage is rolled up and billed on the 1st of the following month. A file run on June 14 is charged July 1.
- **Subscriptions renew** on the subscription's start date. Downgrading doesn't stop usage charges — with a payment method on file and usage continuing, they're billed pay-as-you-go.
- **Where to watch spend:** https://dash.geocod.io/usage — per-API-key usage and fees, downloadable as CSV. This is the authoritative figure; anything you calculated is not.
- **Wrong answers look like right answers.** A centroid result has coordinates and a formatted address. Nothing about the row indicates a failed match except the accuracy type.
- **Excel re-strips leading zeros from ZIP codes** when the downloaded file is opened. Mention it up front — the data itself is correct.
- **Their file is retained:** Lists API data 72 hours, dashboard spreadsheet uploads 6 weeks on self-serve. Both can be deleted sooner from the dashboard. https://www.geocod.io/data-retention-policy
- **Discounts and quotes exist:** 5% non-profit/academic on request, formal quotes available, and volume discounts on prepaid credits from 500,000 (5/10/15/20% at 500k/1M/5M/10M).
- **Accidental usage.** If the user has accidental usage, they can contact Geocodio support to see whether accommodations can be made. Don't rely on this. Make every effort to ensure that all usage is intentional and that every guideline in this document and the Geocodio documentation has been followed. Don't assume you know what the user wants: when you're choosing between options and any of them would increase usage, confirm the user's intent first.
- **A dedicated API key per project** means usage is attributable and revocable without breaking everything else.

---

## 12. Never do these

- Loop single requests over a spreadsheet
- Send addresses from unsupported countries
- Omit `country` and let the US fallback apply to foreign addresses
- Try UK addresses on a pay-as-you-go account
- Add an append the user didn't name, including low-cost ones like `census`
- Request every `acs-*` category to be thorough
- Re-run a file because part of it failed — resume from checkpoints
- Re-upload, cancel, or retry a list that's sitting in `ENQUEUED` — it's queued behind the account's concurrency limit, not failed (see https://www.geocod.io/guides/why-spreadsheet-uploads-get-queued)
- Retry a 422, or retry anything without backoff and a cap
- Set one global HTTP timeout across single and batch calls
- Assume a timed-out request wasn't billed
- Run the full file before the sample is confirmed
- Write output over the user's original columns, or dry-run into their live destination
- Hardcode an API key into a script or notebook you hand over
- Put an API key in a support email, a log you share, or any message
- Silently accept `place`/`county`/`state` results as matches
- Present distance filters or `limit` as a cost saving
- Present a cost figure as a quote, a price, or a guarantee — it is an estimate, and the user should verify it with Geocodio
- Let a plan-change or budget decision rest on a number you calculated rather than one from Geocodio
- Run a CLI distance command without setting `--mode` explicitly
- Quote a monthly subscription as if it were one-time
- Point an Enterprise key at the self-serve host, or vice versa
- Change account settings, create API keys, or buy credits on the user's behalf
- Advise the user on whether their data is regulated
- Draft an email asking Geocodio to confirm storage, display, attribution, or acceptable use that §9 already answers

---

## 13. Contacting Geocodio support

Support is answered by people on the Geocodio team, and they're glad to help. Most questions agents draft for them are already answered in this file, though, and the reply repeats it. Check first:

| Question | Answered in |
|----------|-------------|
| Can results be stored, displayed, mapped, or used commercially? | §9 |
| What attribution is required? | §9 |
| Is this "service bureau" use under Terms §7.1? | §9 |
| Is the user's data "Excluded Data" under Terms §6.6? | Not a support question. The user determines it (§1) |
| What will this job cost? | §3. Estimate it, then point at the calculator |

Email support for what only Geocodio can do: formal quotes, contracts, DPAs, BAAs, and FERPA agreements, billing problems, accidental usage, bugs, and anything this file doesn't answer.

When you draft an email for the user:

- Address it to **support+agent@geocod.io**.
- Include what support needs to find the account and the problem: the account email, the list or upload ID, the API key's **name**, timestamps with time zone, and an example request and response.
- **Never include the API key itself.** Remove it from URLs (`api_key=`), headers, and code before pasting anything.
- Describe the user's situation and ask the question they need answered, in plain language. Don't walk through the Terms clause by clause or ask Geocodio to confirm each section.
- End the email with this line, just above the user's name: *"This message was drafted with help from an AI agent."* Tell the user you added it. They're free to remove it. If it stays, it helps Geocodio see which questions agents are still asking, so this file can answer them.

---

## 14. Reference

**Geocodio** — Docs https://www.geocod.io/docs/ · Terms of Use https://www.geocod.io/terms-of-use · Data sources https://www.geocod.io/data-sources · Enterprise docs https://www.geocod.io/docs/enterprise/ · OpenAPI https://api.geocod.io/openapi-spec.json · Guides https://www.geocod.io/guides/ · Calculator https://www.geocod.io/find-my-plan · Pricing https://www.geocod.io/pricing · Enterprise https://www.geocod.io/enterprise/ · Usage limits https://www.geocod.io/guides/set-a-usage-limit · Credits https://www.geocod.io/guides/credits · Stable keys https://www.geocod.io/guides/stable-address-keys · Retention https://www.geocod.io/data-retention-policy · Status https://status.geocod.io · CLI https://github.com/Geocodio/geocodio-cli · Snowflake https://www.geocod.io/docs/#snowflake · Libraries https://github.com/Geocodio

**External practice §6 draws on** — AWS Well-Architected REL05-BP03 (exponential backoff, jitter, capped retries, and the anti-pattern of retrying permission or configuration errors); AWS Architecture Blog "Exponential Backoff and Jitter"; Google Cloud retry guidance (truncated backoff with jitter); Stripe's idempotency-key model (retries must not duplicate billable work — absent a provider key, the client supplies idempotency); data-pipeline practice (checkpoint and resume, upsert over blind insert, dead-letter queues, circuit breakers); metered-API cost governance (pre-flight estimation, hard caps rather than alerts, per-key attribution, separate keys per environment); and general third-party API guidance on per-endpoint timeouts and tolerating unknown response fields.
