# gtm:free API and MCP

Google Maps Scraper (`google-maps-scraper`) is the only available service. Read the [OpenAPI 3.1 JSON contract](https://gtmfree.com/openapi.json) for exact input schemas and limits, or the [interactive reference](https://gtmfree.com/docs/api/).

## Account and REST authentication

1. [Sign up](https://gtmfree.com/account/) and verify your email.
2. Create an API key in your account. Save it securely; it is shown only once.
3. Call `https://gtmfree.com/api/v1` with `Authorization: Bearer YOUR_API_KEY`.

Website session cookies and MCP OAuth tokens are not REST API keys. Never use a server-side authentication-provider secret as a client credential.

## Jobs

- `GET /api/v1/tools`: Public service catalogue, subject to network policy.
- `GET /api/v1/me`: Check your API key and account limits.
- `POST /api/v1/tools/google-maps-scraper/jobs`: Submit a job. Use the OpenAPI JSON body. Every accepted submission creates a new job.
- `GET /api/v1/jobs`: List your jobs with cursor pagination.
- `GET /api/v1/jobs/{job_id}`: Read status. Poll no faster than every two seconds and honor `Retry-After`.
- `GET /api/v1/jobs/{job_id}/events`: Follow REST server-sent events.
- `GET /api/v1/jobs/{job_id}/results`: Read paginated retained results.
- `GET /api/v1/jobs/{job_id}/files/{format}`: Download a requested retained export.

Only submit a scraping job when the user authorizes the work. Job acceptance is asynchronous. A timeout does not prove submission failed. `submission_unknown` means admission is uncertain; inspect the returned job or list your jobs; do not submit again to recover it. Results and exports require ownership and are retained on a best-effort basis.

## Large results and streaming exports

Each job supports up to **100,000 businesses**. Read `/results?limit=100` and follow `next_cursor` until null to retrieve every retained record; a shorter page does not mean the end. REST pages remain bounded to 1 MiB and MCP pages use a smaller budget.

Terminal status, result pages and MCP export chunks may include optional `coverage`
evidence. Preserve its stopping reason and partial/resumable state; older jobs can
omit it. `sampling_complete` means the server's sampling heuristic finished, while
`exhaustive` remains false. Google can hide businesses at other zoom levels and
return loosely related keyword matches. Pending counts are lower bounds and
progress totals can be null, so do not invent a completion percentage. Budget
stops keep available results and exports under a failed job. No read or download
automatically resumes a search; a later submission uses normal admission limits.

Request JSON, CSV or XLSX exports when submitting the job. File downloads stream from durable storage in bounded chunks. A normal GET retrieves the complete file; resume using `Range: bytes=131072-` (HTTP 206). Only single ranges with an explicit starting offset are supported. Invalid or out-of-file ranges return 416. Python's `gtmfree.streaming.iter_job_export` and TypeScript's `iterJobExport` consume bytes incrementally; ordinary generated download methods buffer the file.

Downloads share 4 GiB per user per UTC hour, two concurrent transfers per user and a 15-minute transfer deadline. Requested bytes are charged before delivery, including disconnected or repeated transfers. Read quota/reset headers and honor `Retry-After`. All interfaces retain authentication, ownership, country/Spamhaus screening and shared request limits. The entity cap, an 8 KiB business-record cap and a 1 GiB total retained data/export budget prevent oversized work; reaching a cap produces an explicit failed/partial result rather than claiming complete coverage. The boundary-download limit is separate from business result/export limits.

## MCP

Use Streamable HTTP at `https://gtmfree.com/mcp`. Sign up on the website and verify your email first, then log in with your gtm:free account from a client supporting OAuth. Do not paste a REST API key into an OAuth token field.

The MCP endpoint exposes `listTools`, `discoverRegions`, `createGoogleMapsJob`, `listJobs`, `getJob`, `getJobResults`, and `getJobExport`. The only scraping service is Google Maps. After a lost response, list your jobs before submitting again. Follow `getJobResults.next_cursor` to read all records. Retrieve requested JSON/CSV/XLSX exports with `getJobExport`, decoding and concatenating `content_base64` chunks in offset order until `next_offset` is null. Default chunks contain up to 65,536 bytes, with a maximum of 131,072; each chunk shares the REST download byte budget and connection limits. These tools use your MCP OAuth login and need no REST API key.

OAuth discovery: [protected resource metadata](https://gtmfree.com/.well-known/oauth-protected-resource/mcp). Public schemas: [MCP server card](https://gtmfree.com/.well-known/mcp/server-card.json). Listing public schemas does not authorize tool execution.

## SDKs

[Download the generated Python and TypeScript packages](https://gtmfree.com/sdk/). Both wrap REST and use your personal API key. npm and PyPI releases have not been published yet. The clients do not automatically retry submissions or poll jobs.

## Limits and availability

You can run one job at a time per tool across REST, MCP, SDKs, and all your API keys. Request budgets, global capacity limits, maximum work size, and transfer limits also apply. Honor `429` and `Retry-After`; `503` can indicate unavailable capacity or a dependency. Never create multiple accounts or rotate identities to bypass limits.

Country and Spamhaus network restrictions apply to every endpoint, including documentation and metadata. Datacenter screening applies only to enrollment. See [Terms of Use](https://gtmfree.com/terms/) for authorized jurisdictions and acceptable use. This is a free service with no availability, accuracy, completion, or retention guarantee.


See the [limits and response headers guide](https://gtmfree.com/docs/limits.md) for all action budgets, admission costs, remaining allowances, and reset times.

## Discover countries and subdivisions

`GET /api/v1/regions` (API key) and MCP `discoverRegions` (OAuth) share the same geographic catalog. They create no scraping job and make no Google request.

1. List countries and inspect their `supported_levels` and `loaded_levels`. Availability varies by country; ADM1 does not have the same local meaning everywhere.
2. Search a country and level: `GET /api/v1/regions?country=DEU&level=1&query=Bavaria&load=true`. The default `load=false` never downloads a new boundary layer. A cold lookup can return `503 region_loading` with `Retry-After: 5` while indexing continues. Retry the same lookup after that delay; it joins the existing operation instead of starting another. At most one catalog operation runs globally.
3. Use an exact returned ID, or a qualified name, in a scraping request:

```json
{"parameters":{"query":"coffee shop","region":{"country":"DEU","level":1,"name":"Bavaria"}}}
```

Alternatively use `"region":{"id":"<exact ID from discovery>"}`. Treat IDs as opaque. Structured selections cannot be combined with custom `geodata`. Whole-country strings and custom GeoJSON remain supported; bare French department codes are replaced by worldwide discovery.

An unloaded country/level returns `items: []` and `complete: false`; that does not mean it contains no subdivisions. With the default administrative kind, global text searches cover only countries and previously loaded subdivisions (`scope: loaded`, `complete: false`). Choose a country and load its supported level for a complete search. Results are paginated with `limit` (1–100), `offset` and `next_offset`.

Names never resolve to an arbitrary first match. `ambiguous_region`, `region_not_found` and `unsupported_region_level` describe selection failures; `matches` offers up to 20 labeled choices when available. Discovery errors are immediate; job-time errors appear in `getJob.error`. Prefer `display_name`/`label`, preserve local names and diacritics, and do not invent an English translation when `english_name` is null. `parent_status` records uncertain or inferred hierarchy.

Every discovery response preserves geoBoundaries attribution and original layer/source/license notices. Structured scrape results include the selected `region`, including cache hits. REST/SDK result pages and MCP `getJobResults` expose it; MCP `getJobExport` repeats it on each chunk. Binary REST/SDK downloads link to their result metadata with `Link: <…/results>; rel="describedby"`. Preserve these notices when displaying or sharing selected geography.

Administrative boundaries: [geoBoundaries gbOpen](https://www.geoboundaries.org/) ([CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)); retain each layer's original source/license notices. Coverage follows the provider's available layers and does not claim GADM-equivalent coverage.

### Cities worldwide

Use `GET /api/v1/regions?kind=city&query=Munich` or MCP
`discoverRegions({"kind":"city","query":"Lyon"})` to search the global GeoNames
city catalog. English, local and alternate names are searchable. Country and parent
labels disambiguate cities with the same name. City `level` is null.

Load one exact returned city ID, for example
`GET /api/v1/regions?id=geonames:2867714&load=true&preview=true`.
Inspect `city.boundary_status` and `boundary_reason`; unavailable or ambiguous
municipal boundaries are explicit. A centroid, bbox or broader area is never
substituted. Previews are bounded display geometry; jobs use the full municipal
polygon, including holes and disjoint parts. A complex preview can have null
geometry while its exact boundary remains usable.

Submit `{"parameters":{"query":"coffee shop","region":{"id":"geonames:2996944"}}}`
for Lyon, or use an exact `{"kind":"city","country":"FRA","name":"Lyon"}`
region selector. The Python and TypeScript Regions API SDKs support `kind` and
`preview`. Preserve `city_catalog` and returned GeoNames/OSM attribution.
`scope=cities, complete=true` describes the searched gazetteer snapshot, not
universal municipal polygon coverage or an exhaustive business inventory.
The normal shared geographic lookup/load limits continue to apply.
