# Batch Geocode — Google / Mapbox / HERE → GeoJSON (`ingenious_quip_bxq/batch-geocode`) Actor

Batch forward & reverse geocoding via the official Google Geocoding, Mapbox and HERE APIs (bring your own key). Lat/lng, formatted address, components, accuracy; GeoJSON + Markdown report; optional Distance Matrix. No Maps scraping. 256 MB. Not-found & failed rows free.

- **URL**: https://apify.com/ingenious_quip_bxq/batch-geocode.md
- **Developed by:** [新世紀書僮](https://apify.com/ingenious_quip_bxq) (community)
- **Categories:** Developer tools, Automation, Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 geocode results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Batch Geocode — Google / Mapbox / HERE → GeoJSON

Turn a list of addresses into **latitude / longitude** (forward geocoding) or coordinates into **addresses** (reverse geocoding) using the **official** geocoding APIs of Google Maps Platform, Mapbox or HERE — with **your own API key**. Get a clean dataset row per input, a **GeoJSON FeatureCollection** ready for any map tool, and a short **Markdown report**. Optional **Google Distance Matrix** (origin × destination distance and travel time).

- ✅ Official HTTP APIs only — no Google Maps page scraping, no headless browser
- ✅ Bring your own key: you pay your provider directly at your provider's rates and quotas
- ✅ 256 MB default memory, fast and cheap to run
- ✅ **Not-found and failed rows are free** (ZERO_RESULTS, invalid address, missing/rejected key, rate limit after retries)
- ✅ GeoJSON + Markdown + JSON summary in the key-value store

### Who is it for?

Logistics and delivery teams, store-locator and real-estate data, CRM / CDP address cleaning, data pipelines that need coordinates for maps, BI tools or spatial joins.

### Providers

| `provider` | API called | Key you need |
|---|---|---|
| `google` (default) | Google Geocoding API `maps.googleapis.com/maps/api/geocode/json` | Maps Platform API key with **Geocoding API** enabled (billing on) |
| `mapbox` | Mapbox Geocoding v5 `api.mapbox.com/geocoding/v5/mapbox.places` | Mapbox access token |
| `here` | HERE Geocoding & Search v7 `geocode.search.hereapi.com/v1/geocode` (+ `revgeocode`) | HERE REST API key |

#### How to get a key

1. **Google Maps Platform**
   1. Open <https://console.cloud.google.com/apis/library/geocoding-backend.googleapis.com>, pick or create a project and click **Enable**.
   2. Link a **billing account** to the project (<https://console.cloud.google.com/billing>). Google requires billing for the Geocoding API even if you stay within the free monthly usage; without it every request returns `REQUEST_DENIED`.
   3. *APIs & Services → Credentials → Create credentials → API key*.
   4. Under **API restrictions**, restrict the key to **Geocoding API** (add **Distance Matrix API** only if you use the matrix option: <https://console.cloud.google.com/apis/library/distance-matrix-backend.googleapis.com>). Do not add HTTP-referrer restrictions — requests come from Apify servers, not a browser.
   5. Optional but recommended: set a **budget alert** (*Billing → Budgets & alerts*) and per-day **quotas** (*APIs & Services → Geocoding API → Quotas*) so a large batch can never surprise you. Current prices and free usage: <https://mapsplatform.google.com/pricing/>.
2. **Mapbox** — sign in at <https://account.mapbox.com/access-tokens/> and copy the **default public token** (or create a token with the default public scopes). No credit card is needed for the free tier; see <https://www.mapbox.com/pricing> for Geocoding limits.
3. **HERE** — sign up at <https://platform.here.com/>, then *Access manager → Apps → Register new app → API keys → Create API key* (the REST **API key**, not OAuth credentials). Pricing and free tier: <https://www.here.com/get-started/pricing>.

Common key errors (all free, shown as `unauthorized` with a `hint`): Google API not enabled or billing missing (`REQUEST_DENIED`), Mapbox token revoked (HTTP 401), HERE key for a different app/project (HTTP 401/403).

Paste it into **Provider API key** (`apiKey`). It is stored encrypted by Apify, never logged and never written to the output. Without a key, the Actor sends nothing to the provider and marks every row `missing_key` (free), with these instructions in `REPORT.md`.

### Input

| Field | Description |
|---|---|
| `addresses` | Addresses to forward-geocode, one per line |
| `coordinates` | Optional: `"lat,lng"` strings or `{"lat": .., "lng": ..}` objects to reverse-geocode |
| `datasetId` / `keyValueStoreId` + `keyValueRecordKey` / `addressField` | Optional: read the list from another Actor's dataset or a key-value record |
| `provider` | `google` | `mapbox` | `here` |
| `apiKey` | Your provider key (secret) |
| `language`, `region` (Google ccTLD bias), `mapboxCountry` | Optional provider tuning |
| `limit` | Matches per input (best one in the row, others in `alternatives`, no extra charge) |
| `enableDistanceMatrix`, `origins`, `destinations`, `travelMode`, `units`, `maxMatrixElements` | Optional Google Distance Matrix |
| `maxAddresses` (100), `maxConcurrency` (2, max 5), `maxRetries` (3), `requestTimeoutSecs` (30), `stopOnAuthError` (true) | Limits |

Example:

```json
{
  "provider": "google",
  "apiKey": "YOUR_KEY",
  "addresses": ["Brandenburger Tor, Pariser Platz, 10117 Berlin", "350 Fifth Avenue, New York, NY 10118"],
  "coordinates": ["48.8584,2.2945"],
  "language": "en"
}
```

### Output

**Dataset** — one row per address / coordinate:

```json
{
  "input": "Brandenburger Tor, Pariser Platz, 10117 Berlin",
  "inputType": "forward",
  "provider": "google",
  "status": "ok",
  "lat": 52.5162746,
  "lng": 13.3777041,
  "formattedAddress": "Pariser Platz, 10117 Berlin, Germany",
  "locationType": "ROOFTOP",
  "placeId": "ChIJiQnyVcZRqEcRY0xnhE77uyY",
  "providerId": "ChIJiQnyVcZRqEcRY0xnhE77uyY",
  "confidence": null,
  "components": {"country": "Germany", "countryCode": "DE", "admin1": "Berlin", "locality": "Berlin", "postalCode": "10117", "street": "Pariser Platz"},
  "errorClass": null
}
```

- `locationType` / `accuracy`: Google `location_type` (ROOFTOP, RANGE_INTERPOLATED, …), Mapbox `place_type` + `accuracy`, HERE `resultType` + `houseNumberType`.
- `confidence`: Mapbox `relevance` or HERE `queryScore` (Google has no score; use `locationType` and `partialMatch`).
- IDs: `placeId` (Google), `mapboxId` (Mapbox), `hereId` (HERE), and `providerId` for all.
- Failed rows have `status: "failed"`, `errorClass` (`missing_key`, `unauthorized`, `quota_exceeded`, `rate_limited`, `zero_results`, `invalid_request`, `invalid_input`, `timeout`, `network`, `server_error`) and a `hint`.

**Key-value store**

| Key | Content |
|---|---|
| `GEOJSON` | FeatureCollection of successful points (`[lng, lat]`) |
| `REPORT.md` | Counts, result table, hints, how to get a key |
| `SUMMARY` / `OUTPUT` | ok / failed / byClass / charged / duration / peak memory |
| `MATRIX` | Distance Matrix elements (only when enabled) |

### Pricing (pay per event)

| Event | Price |
|---|---|
| Actor start | $0.001 per run |
| `geocode-result` | **$0.002** per successful forward or reverse geocode ($2 per 1,000) |
| `matrix-element` | $0.004 per successful Distance Matrix element (optional) |

Not charged: missing key, REQUEST_DENIED / 401 / 403, OVER_DAILY_LIMIT, 429 after retries, ZERO_RESULTS / not found, invalid input, timeouts. Your provider bills its own API usage to your key separately (Google, Mapbox and HERE all have free monthly tiers).

### Reliability

- 429 / `OVER_QUERY_LIMIT` / 5xx / `UNKNOWN_ERROR` / timeouts are retried with exponential backoff (honours `Retry-After`).
- After the provider rejects the key, remaining inputs are marked failed without being sent (`stopOnAuthError`), so a typo in the key doesn't burn your quota.
- Duplicate addresses (case/whitespace-insensitive) and coordinates are geocoded once.

### Terms of use

You call the provider with your own key, so your provider's terms apply to the results (for example, Google Maps Platform terms limit caching and require results to be used with a Google map when displayed on a map; Mapbox temporary geocoding results may not be stored permanently). Check your provider's terms for your use case.

### FAQ

**Does it scrape Google Maps?** No. Only the documented HTTP geocoding endpoints, authenticated with your key.

**Why do I need my own key?** Geocoding providers require a billing account per user. You keep your provider's free tier, quotas and pricing.

**What happens without a key?** Nothing is sent; each row is `missing_key`, free, and `REPORT.md` explains how to get a key.

# Changelog

This Actor's version history is a separate document: https://apify.com/ingenious_quip_bxq/batch-geocode/changelog.md

# Actor input Schema

## `addresses` (type: `array`):

One address per line, e.g. '1600 Amphitheatre Pkwy, Mountain View, CA'. Duplicates (case-insensitive) are removed.

## `coordinates` (type: `array`):

Optional. Array of "lat,lng" strings or {"lat": 52.5163, "lng": 13.3777} objects. Out-of-range or malformed values become free invalid_input rows.

## `datasetId` (type: `string`):

Optional. Read addresses from another Actor's dataset. Uses the field set in 'Address field', else address / fullAddress / query / location / formattedAddress; rows with lat+lng (or latitude+longitude) and no address are reverse-geocoded.

## `keyValueStoreId` (type: `string`):

Optional. Read addresses from a key-value record: JSON array, {"addresses": \[...], "coordinates": \[...]}, or plain text with one address per line.

## `keyValueRecordKey` (type: `string`):

Record key inside the key-value store (default INPUT_ADDRESSES).

## `addressField` (type: `string`):

Optional. Name of the field holding the address in dataset items or JSON objects.

## `provider` (type: `string`):

Which official geocoding API to call with your key.

## `apiKey` (type: `string`):

Google: Maps Platform API key with the Geocoding API enabled (+ billing). Mapbox: access token. HERE: REST API key. Stored encrypted and never logged or written to output. WITHOUT a key nothing is sent and rows are failed + free (missing_key).

## `language` (type: `string`):

Optional language code for results, e.g. en, de, zh-TW (Google `language`, Mapbox `language`, HERE `lang`).

## `region` (type: `string`):

Optional ccTLD region bias for Google forward geocoding, e.g. uk, de, tw.

## `mapboxCountry` (type: `string`):

Optional comma-separated ISO 3166-1 alpha-2 codes for Mapbox, e.g. us,ca.

## `limit` (type: `integer`):

Best match goes into the row; extra matches are added to `alternatives` (no extra charge).

## `enableDistanceMatrix` (type: `boolean`):

Also compute origin × destination distance/duration with the Google Distance Matrix API (your key must have that API enabled). Results go to the MATRIX record and REPORT.md; each OK element is a matrix-element event.

## `origins` (type: `array`):

Addresses or 'lat,lng' strings.

## `destinations` (type: `array`):

Addresses or 'lat,lng' strings.

## `travelMode` (type: `string`):

Distance Matrix travel mode.

## `units` (type: `string`):

Text units for distances (values are always meters / seconds).

## `maxMatrixElements` (type: `integer`):

Cap on origins × destinations (requests are chunked within Google's 25×25 / 100-element limits).

## `maxAddresses` (type: `integer`):

Stop after this many inputs (addresses + coordinates). 0 = no limit.

## `maxConcurrency` (type: `integer`):

Parallel provider requests.

## `maxRetries` (type: `integer`):

Retries for 429 / OVER_QUERY_LIMIT (honours Retry-After), 5xx / UNKNOWN_ERROR and timeouts with exponential backoff. 401/403/REQUEST_DENIED/ZERO_RESULTS are not retried.

## `requestTimeoutSecs` (type: `integer`):

Timeout per provider request.

## `stopOnAuthError` (type: `boolean`):

After the provider rejects the key (REQUEST_DENIED / 401 / 403 / OVER_DAILY_LIMIT), mark the remaining inputs as failed without sending them (saves your quota). All free.

## Actor input object example

```json
{
  "addresses": [
    "Brandenburger Tor, Pariser Platz, 10117 Berlin",
    "350 Fifth Avenue, New York, NY 10118"
  ],
  "keyValueRecordKey": "INPUT_ADDRESSES",
  "provider": "google",
  "limit": 1,
  "enableDistanceMatrix": false,
  "travelMode": "driving",
  "units": "metric",
  "maxMatrixElements": 100,
  "maxAddresses": 100,
  "maxConcurrency": 2,
  "maxRetries": 3,
  "requestTimeoutSecs": 30,
  "stopOnAuthError": true
}
```

# Actor output Schema

## `results` (type: `string`):

Dataset rows: input, provider, status, lat, lng, formattedAddress, locationType, accuracy, providerId, components, confidence, errorClass, hint.

## `geojson` (type: `string`):

Successful points as GeoJSON Point features.

## `reportMarkdown` (type: `string`):

Summary counts, result table, failure hints and how to get a key.

## `summary` (type: `string`):

ok, failed, byClass, apiKeyProvided, charged, durationSecs, peakMemoryMb.

## `matrix` (type: `string`):

Only when enableDistanceMatrix is on: origin, destination, distanceMeters, durationSecs, status.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "addresses": [
        "Brandenburger Tor, Pariser Platz, 10117 Berlin",
        "350 Fifth Avenue, New York, NY 10118"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ingenious_quip_bxq/batch-geocode").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "addresses": [
        "Brandenburger Tor, Pariser Platz, 10117 Berlin",
        "350 Fifth Avenue, New York, NY 10118",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("ingenious_quip_bxq/batch-geocode").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "addresses": [
    "Brandenburger Tor, Pariser Platz, 10117 Berlin",
    "350 Fifth Avenue, New York, NY 10118"
  ]
}' |
apify call ingenious_quip_bxq/batch-geocode --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ingenious_quip_bxq/batch-geocode"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/KC12JVXYuVMr3HGHe/builds/pL8G5pQP5V11cEqG5/openapi.json
