# TerraLab documentation > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. TerraLab makes two kinds of products: QGIS plugins that bring AI into your GIS projects, and an MCP server and REST API that give AI agents and code geodata for any place. One account covers both. ## QGIS plugins AI inside QGIS, on your own layers. The models run on TerraLab servers: no GPU, no Python setup. - [Install and sign in](https://terra-lab.ai/docs/plugins/install) The plugin manager, the QGIS version each plugin needs, the browser sign-in. - [AI Agent](https://terra-lab.ai/docs/plugins/ai-agent) A GIS task done in your project from one sentence. - [AI Segmentation](https://terra-lab.ai/docs/plugins/ai-segmentation) Objects in imagery turned into polygons, from a click or a word. - [AI Edit](https://terra-lab.ai/docs/plugins/ai-edit) An aerial image changed from a plain-language instruction. ## MCP server and API 29 geodata tools for Claude, ChatGPT, Cursor and any agent, or for your code over HTTPS. Every result names its sources and licences. - [Quickstart](https://terra-lab.ai/docs/quickstart) A first call in under five minutes. - [Connect an agent](https://terra-lab.ai/docs/mcp) The command or link for Claude, ChatGPT, Claude Code, Codex, Cursor and VS Code. - [Authentication](https://terra-lab.ai/docs/authentication) API keys for code, OAuth sign-in for agents. - [API reference](https://terra-lab.ai/docs/api) Every tool with its parameters and samples in curl, Python and JavaScript. ## Help - [Pricing and free calls](https://terra-lab.ai/docs/pricing) 100 calls included every month, then pay per call. - [Errors and rate limits](https://terra-lab.ai/docs/errors) Every error code and what to do. - [Data sources and licences](https://terra-lab.ai/docs/data-sources) Where the data comes from. - [Support](https://terra-lab.ai/docs/support) Who answers and what to send. ## For AI agents Every page is also served as markdown: add `.md` to its URL, or send `Accept: text/markdown`. The index of all pages is [/docs/llms.txt](https://terra-lab.ai/docs/llms.txt), and every page in one file is [/docs/llms-full.txt](https://terra-lab.ai/docs/llms-full.txt). Source: https://terra-lab.ai/docs · Updated 2026-10-27 --- # QGIS plugins > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Three TerraLab plugins add AI to QGIS. AI Agent carries out a GIS task from one sentence, AI Segmentation turns imagery into vector polygons, and AI Edit changes what an image shows. The models run on TerraLab servers, so any work laptop that runs QGIS is enough: no GPU, no Python setup. ## Which plugin to use | You want to | Plugin | Free every month | | --- | --- | --- | | Get a task done in your project: find data, run processing, build the map | [AI Agent](https://terra-lab.ai/docs/plugins/ai-agent) | 5 tasks | | Outline buildings, trees, cars or any object in imagery as polygons | [AI Segmentation](https://terra-lab.ai/docs/plugins/ai-segmentation) | 1.5 km² of Automatic detection and 20 object detections | | Show a change on an aerial image: a project, trees, a clean site plan | [AI Edit](https://terra-lab.ai/docs/plugins/ai-edit) | 5 images | All three install from the official QGIS plugin repository and share one TerraLab account. See [Install and sign in](https://terra-lab.ai/docs/plugins/install). ## Plugins or MCP server The plugins work inside QGIS, on your own layers and project. The [MCP server](https://terra-lab.ai/docs/mcp) brings TerraLab data and GIS files to an AI agent such as Claude or ChatGPT, outside QGIS; its files then open in QGIS. Both use the same account. ## Plans Each plugin has a free allowance that renews every month and a Pro plan; one pack covers all three. Prices are on the [pricing page](https://terra-lab.ai/pricing), and every Pro plan starts with a 7-day free trial. Source: https://terra-lab.ai/docs/plugins · Updated 2026-10-27 --- # Install and sign in > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Install a TerraLab plugin from the QGIS plugin manager, then sign in once in your browser. It takes about a minute and needs no card. ## Requirements | Plugin | QGIS | System | | --- | --- | --- | | AI Agent | 3.28 or later | Windows, macOS or Linux, with an internet connection | | AI Segmentation | 3.22 or later | Windows, macOS or Linux, with an internet connection | | AI Edit | 3.22 or later | Windows, macOS or Linux, with an internet connection | No GPU and no Python package to install: the models run on TerraLab servers. ## Install 1. In QGIS, open **Plugins**, **Manage and Install Plugins**. 2. Search for **AI Agent by TerraLab**, **AI Segmentation** or **AI Edit**. 3. Click **Install**. The same plugins can be downloaded as a zip from plugins.qgis.org: [AI Agent](https://plugins.qgis.org/plugins/AI_Agent/), [AI Segmentation](https://plugins.qgis.org/plugins/AI_Segmentation/), [AI Edit](https://plugins.qgis.org/plugins/AI_Edit/). In QGIS, **Install from ZIP** installs that file. ## Sign in 1. Open the plugin panel. AI Agent opens with `Ctrl+Alt+A`. 2. Click **Sign in**. Your browser opens terra-lab.ai. 3. Sign in or create a free account, confirm, and go back to QGIS. The account links itself and the free allowance shows in the panel. One account covers the three plugins, the [MCP server](https://terra-lab.ai/docs/mcp) and the API. Usage and plans are on your [dashboard](https://terra-lab.ai/dashboard). ## Update QGIS lists available updates under **Plugins**, **Manage and Install Plugins**, **Upgradeable**. Install them there; your sign-in is kept. ## When something goes wrong - A `DLL load failed` error on Windows: see [DLL load failed while importing _C](https://terra-lab.ai/blog/dll-load-failed-while-importing-c-qgis). - QGIS installed with Flatpak on Linux: see [No module named pip](https://terra-lab.ai/blog/qgis-flatpak-no-module-named-pip). - Anything else: write to [support](https://terra-lab.ai/docs/support). Source: https://terra-lab.ai/docs/plugins/install · Updated 2026-10-27 --- # AI Agent > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. AI Agent carries out a written GIS task in the QGIS project you have open. It finds the data, runs the processing and builds the map, and one click undoes anything it changed. ## Use it 1. Open the panel with `Ctrl+Alt+A` and sign in. See [Install and sign in](https://terra-lab.ai/docs/plugins/install). 2. Ask in one message, for example "Show the buildings of Sommières that stand in a flood zone." It already sees your layers, fields, coordinate system and map extent. 3. Follow the plan as it runs: the data it found, the algorithms, the styles. It renders the result and fixes what reads wrong before it answers. 4. Keep the result, or undo it from its checkpoint. ## What it works with - Your own layers: GeoPackage, Shapefile, GeoTIFF, CSV and any layer QGIS opens. - Open datasets it downloads for your area, each with its licence and attribution. - QGIS processing tools and the plugins installed in your QGIS. ## Free tasks and Pro A task is one request the agent carries out. You get 5 free tasks every month. Pro adds more tasks a month, with a 7-day free trial; prices are on the [pricing page](https://terra-lab.ai/pricing). ## Learn more - [AI Agent for QGIS: the complete guide](https://terra-lab.ai/blog/ai-agent-complete-guide): first request, examples, settings and five real tasks. - [The AI Agent benchmark](https://terra-lab.ai/blog/ai-agent-qgis-plugin-geospatial-benchmark): how it scored on Google's geospatial benchmarks. - [Product page](https://terra-lab.ai/ai-agent). Source: https://terra-lab.ai/docs/plugins/ai-agent · Updated 2026-10-27 --- # AI Segmentation > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. AI Segmentation turns imagery into vector polygons in QGIS. Click one object to get its outline, or name what you want, such as "buildings", "solar panels" or "trees", and get every match across an area. ## Two modes | Mode | How | Where it runs | Counter | | --- | --- | --- | --- | | Semi-Auto | Click an object, get its outline | On your computer | None, free | | Automatic | Name an object, get every match in a drawn zone | On TerraLab servers, no GPU needed | km² and object detections | ## Use it 1. Pick the imagery layer and draw the zone on the map. The panel shows its size in km² before anything runs. 2. Type a word like building, tree or car, or pick one from the Library. Draw an example on the map when the object is unusual. 3. Keep what you need with the confidence and size filters, correct the rest, then export one vector layer with its attribute table. ## Free allowance and Pro Every month: 1.5 km² of Automatic detection and 20 object detections, no card. Semi-Auto has no counter. Pro covers larger areas with a 7-day free trial; prices are on the [pricing page](https://terra-lab.ai/pricing). ## Learn more - [AI Segmentation: the complete guide](https://terra-lab.ai/blog/ai-segmentation-complete-guide). - [Detect building footprints in QGIS](https://terra-lab.ai/blog/detect-building-footprints-qgis). - [Product page](https://terra-lab.ai/ai-segmentation). Source: https://terra-lab.ai/docs/plugins/ai-segmentation · Updated 2026-10-27 --- # AI Edit > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. AI Edit generates a new image of an area of your own imagery from a plain-language instruction: show a redevelopment before it is built, plant a street with trees, or turn an orthophoto into a clean site plan. The result lands in QGIS as a georeferenced raster on the same extent and coordinate system as the source, marked as AI-generated. ## Use it 1. Draw the zone to change on any raster layer: an orthophoto, a satellite basemap or your own drone image. 2. Describe what should be different, or start from a prompt in the Library. Mark up the map to point at one spot. 3. The result lands on the map as a new layer, aligned with the source, up to 4K. Compare it, refine it from a previous version, or vectorize it into polygons. ## Free images and Pro You get 5 free images every month, no card. Pro adds more images a month, with a 7-day free trial; prices are on the [pricing page](https://terra-lab.ai/pricing). ## Learn more - [AI Edit: the complete guide](https://terra-lab.ai/blog/ai-edit-complete-guide). - [Turn a satellite image into a site plan](https://terra-lab.ai/blog/satellite-image-to-site-plan). - [Product page](https://terra-lab.ai/ai-edit). Source: https://terra-lab.ai/docs/plugins/ai-edit · Updated 2026-10-27 --- # Quickstart > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Two paths: call the REST API from code, or add the MCP server to an AI agent. Both use the same account and the same 100 free calls a month. ## Call the API from code ### Create an API key 1. Sign in at [terra-lab.ai/login](https://terra-lab.ai/login). An account is free and needs no card. 2. Open [Dashboard, MCP](https://terra-lab.ai/dashboard/mcp), then **Advanced**, **API keys**. 3. Name the key, accept the API terms and click **Create key**. 4. Copy the key now: it starts with `tlk_` and is shown only once. Keep it in an environment variable, never in your code: ```bash export TERRALAB_API_KEY="tlk_your_key_here" ``` ### Check the key `GET /v1/me` returns your account and the calls you have left this month. ```bash curl https://mcp.terra-lab.ai/v1/me \ -H "Authorization: Bearer $TERRALAB_API_KEY" ``` A `401` means the key is missing, mistyped or revoked. ### Make your first tool call Find the coordinates of an address with `find_places`: ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/find_places \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query":"12 rue de la Paix, Annecy"}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/find_places", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={"query": "12 rue de la Paix, Annecy"}, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/find_places', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ query: '12 rue de la Paix, Annecy' }), }); const data = await response.json(); console.log(data.structuredContent); ``` The answer is a JSON object: `structuredContent` holds the result with its `sources`, `content` the same result as text, and `isError` says whether the tool refused the request. ### Go further - Extract buildings and roads for an area with [extract_open_data](https://terra-lab.ai/docs/api/extract_open_data). - Get elevation, a profile or contour lines with [get_elevation](https://terra-lab.ai/docs/api/get_elevation). - Browse all 29 tools in the [API reference](https://terra-lab.ai/docs/api). ## Add TerraLab to an AI agent In Claude Code, one command: ```bash claude mcp add --transport http terralab https://mcp.terra-lab.ai/mcp ``` Then run `/mcp` in Claude Code and sign in to TerraLab. For Claude, ChatGPT, Cursor, Codex and VS Code, see [MCP server](https://terra-lab.ai/docs/mcp). Ask, for example: "Show the buildings within 500 m of Lyon Part-Dieu station and give me a GeoPackage." Source: https://terra-lab.ai/docs/quickstart · Updated 2026-10-27 --- # Connect an agent > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. TerraLab is a remote MCP server over Streamable HTTP. One URL works in every agent: ```text https://mcp.terra-lab.ai/mcp ``` The first time, your agent asks you to sign in to TerraLab. Your free calls and balance show on [Dashboard, MCP](https://terra-lab.ai/dashboard/mcp). ## Claude Open the prefilled dialog: [add TerraLab to Claude](https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=TerraLab&connectorUrl=https%3A%2F%2Fmcp.terra-lab.ai%2Fmcp). Click **Add**, then **Connect**, and sign in to TerraLab. Or in Claude, open **Settings**, **Connectors**, **Add custom connector**, and paste the server URL. ## ChatGPT Open the [TerraLab app in ChatGPT](https://chatgpt.com/apps/terralab/asdk_app_6abeb2f16190819199cc1be0ed69e8d4), click **Add**, then start your message with `@TerraLab`. ## Claude Code Run this in your terminal: ```bash claude mcp add --transport http terralab https://mcp.terra-lab.ai/mcp ``` Then run `/mcp` in Claude Code and sign in to TerraLab. Ready-made GIS workflows come as a plugin: ```text /plugin marketplace add TerraLabAI/mcp /plugin install terralab@terralab-ai-apps ``` ## Codex ```bash codex mcp add terralab --url https://mcp.terra-lab.ai/mcp ``` Codex adds the server and opens the TerraLab sign-in in your browser. It then works in the CLI, the IDE extension and the app. The same entry in `~/.codex/config.toml`: ```toml [mcp_servers.terralab] url = "https://mcp.terra-lab.ai/mcp" ``` ## Cursor Open the install link: [add TerraLab to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=terralab&config=eyJ1cmwiOiJodHRwczovL21jcC50ZXJyYS1sYWIuYWkvbWNwIn0%3D). Cursor shows the server: click **Install**, then sign in when it asks. Or add a remote server in Cursor's MCP settings, in `.cursor/mcp.json`: ```json { "mcpServers": { "terralab": { "url": "https://mcp.terra-lab.ai/mcp" } } } ``` ## VS Code Open the install link: [add TerraLab to VS Code](vscode:mcp/install?%7B%22name%22%3A%22terralab%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fmcp.terra-lab.ai%2Fmcp%22%7D). Click **Install**, sign in to TerraLab, then ask Copilot in agent mode. Or in `.vscode/mcp.json`: ```json { "servers": { "terralab": { "type": "http", "url": "https://mcp.terra-lab.ai/mcp" } } } ``` ## Other agents Add a remote MCP server with the URL above in any agent that takes one; it opens the TerraLab sign-in. Gemini CLI: ```bash gemini mcp add --transport http -s user terralab https://mcp.terra-lab.ai/mcp ``` ## Tools The server exposes 29 tools. Each one is documented with its parameters and a request example in the [API reference](https://terra-lab.ai/docs/api): the MCP tool and the REST endpoint share the same name and arguments. - **Places**: [Find places and addresses](https://terra-lab.ai/docs/api/find_places), [Convert coordinates](https://terra-lab.ai/docs/api/convert_coordinates), [Measure distance, area, radius or buffer](https://terra-lab.ai/docs/api/measure) - **Map data**: [Extract open map data for an area](https://terra-lab.ai/docs/api/extract_open_data), [Open, map and convert GIS data](https://terra-lab.ai/docs/api/open_gis_file), [Buffer, merge, clip or simplify features](https://terra-lab.ai/docs/api/process_geometry) - **Terrain and routes**: [Elevation, profile and contour lines](https://terra-lab.ai/docs/api/get_elevation), [Road route between places](https://terra-lab.ai/docs/api/get_route), [Area reachable in a travel time](https://terra-lab.ai/docs/api/get_travel_area) - **Imagery and land**: [Satellite image of an area](https://terra-lab.ai/docs/api/get_satellite_image), [Forest type (natural, planted, plantation)](https://terra-lab.ai/docs/api/get_forest_type), [Estimate the population of an area](https://terra-lab.ai/docs/api/estimate_population) - **France**: [French cadastral parcel and zoning](https://terra-lab.ai/docs/api/french_parcel), [French building heights](https://terra-lab.ai/docs/api/french_building_heights), [Natural risks at an address or a point](https://terra-lab.ai/docs/api/get_natural_risks) - **Data catalog**: [Find a dataset in the TerraLab catalogue](https://terra-lab.ai/docs/api/find_datasets), [Extract a hosted dataset for an area](https://terra-lab.ai/docs/api/extract_dataset), [Ask TerraLab to add data it does not have](https://terra-lab.ai/docs/api/request_data) ## What to ask - "Find the coordinates of these 40 addresses and give me a CSV for QGIS." - "Extract the buildings and roads within 1 km of Annecy station as a GeoPackage." - "Elevation profile from Chamonix to the Aiguille du Midi." - "Which natural risks apply at 12 rue de la Paix, Annecy?" Answers show a map in the chat when the agent supports it, and give files that open in QGIS, ArcGIS or Google Earth. ## Troubleshooting - **TerraLab shows as connected in Claude but has no tools.** If you added it before October 26, 2026, remove the connector and add it again: Claude then offers the TerraLab sign-in. - **The agent says the free calls are used.** Top up on [Dashboard, MCP](https://terra-lab.ai/dashboard/mcp) or wait for the 1st of the month. See [Pricing and free calls](https://terra-lab.ai/docs/pricing). - **A tool answers with an error.** Its `message` and `hint` say what to change. See [Errors and rate limits](https://terra-lab.ai/docs/errors). Source: https://terra-lab.ai/docs/mcp · Updated 2026-10-27 --- # Authentication > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Every call needs a TerraLab account, except the two public documents: [`/v1/openapi.json`](https://mcp.terra-lab.ai/v1/openapi.json) and [`/v1/meta/pricing`](https://mcp.terra-lab.ai/v1/meta/pricing). There are two ways to send it: an API key for code, OAuth 2.1 for agents. ## API keys Use a key for scripts, back ends and notebooks, anything that cannot open a browser to sign in. 1. Sign in at [terra-lab.ai/login](https://terra-lab.ai/login). 2. Open [Dashboard, MCP](https://terra-lab.ai/dashboard/mcp), then **Advanced**, **API keys**. 3. Name the key, accept the API terms and click **Create key**. 4. Copy it at once. A key starts with `tlk_`, followed by 48 hexadecimal characters, and is shown only once: TerraLab stores a hash, never the key. Send it in the `Authorization` header: ```bash curl https://mcp.terra-lab.ai/v1/me \ -H "Authorization: Bearer $TERRALAB_API_KEY" ``` Keys do not expire, and an account holds up to 20 live keys. **Revoke** one from the same page when it leaks or a project ends: the next call with it answers `401`. Calls made with a key count against the account that created it. Keep keys out of source control, front-end code and screenshots. Read them from an environment variable or a secret manager. ## OAuth 2.1 for agents AI agents sign in instead of using a key. When an agent first calls `https://mcp.terra-lab.ai/mcp` without a token, the server answers `401` with a `WWW-Authenticate` header that points to its metadata, and the agent opens the TerraLab sign-in in your browser. You approve once; the agent then holds a token. | What | Value | | --- | --- | | Protected resource metadata | `https://mcp.terra-lab.ai/.well-known/oauth-protected-resource` | | Authorization server | `https://terra-lab.ai` | | Server metadata | `https://terra-lab.ai/.well-known/oauth-authorization-server` | | Grant | Authorization code with PKCE (`S256`), public clients | | Client registration | Dynamic registration, or a client ID metadata document | | Scope | `data` | | Access token lifetime | 1 hour, renewed with a refresh token valid 30 days | Most agents handle all of this themselves: you only paste the server URL. See [MCP server](https://terra-lab.ai/docs/mcp). An OAuth access token also works on the REST API, as a Bearer token in the same header. ## Disconnect an agent [Dashboard, MCP](https://terra-lab.ai/dashboard/mcp) lists your **Connected apps**. **Disconnect** revokes that app's access: its next call answers `401` with `access_revoked`. Source: https://terra-lab.ai/docs/authentication · Updated 2026-10-27 --- # API reference > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. The TerraLab REST API serves the same 29 tools as the MCP server, as HTTPS endpoints: each tool is a `POST` with a JSON body, the account endpoints are `GET`. ## Base URL ```text https://mcp.terra-lab.ai ``` Send your API key as a Bearer token on every call except the two public documents. See [Authentication](https://terra-lab.ai/docs/authentication). ## Account and discovery | Endpoint | Auth | What it returns | | --- | --- | --- | | `GET /v1/me` | Key | The account, its calls this month and today, its balance and its activation state. | | `GET /v1/tools` | Key | Every tool with its input and output JSON Schema. | | `GET /v1/openapi.json` | None | This reference as an OpenAPI 3.1 document: [https://mcp.terra-lab.ai/v1/openapi.json](https://mcp.terra-lab.ai/v1/openapi.json). | | `GET /v1/meta/pricing` | None | The price grid as JSON: [https://mcp.terra-lab.ai/v1/meta/pricing](https://mcp.terra-lab.ai/v1/meta/pricing). | Check your key and your remaining calls: ```bash curl https://mcp.terra-lab.ai/v1/me \ -H "Authorization: Bearer $TERRALAB_API_KEY" ``` ```python import os import requests response = requests.get( "https://mcp.terra-lab.ai/v1/me", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, timeout=120, ) response.raise_for_status() print(response.json()) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/me', { headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, }, }); const data = await response.json(); console.log(data); ``` ```json { "account": "3f9c0a1e7b2d4c6a8e0f1b3d", "auth": "key", "today": { "calls": 3, "limit": 100 }, "month": { "calls": 12, "included": 100, "resets_at": "2026-11-01T00:00:00.000Z" }, "balance": { "currency": "EUR", "amount": 0, "micros": 0 }, "activation": { "first_call_at": null, "first_call_client": null } } ``` An illustrative answer for a new account. `month.included` is the number of calls included this calendar month (UTC); later calls are taken from the balance. `today.limit` carries the same number for older clients. ## Tools ### Places | Tool | What it gives | | --- | --- | | [Find places and addresses](https://terra-lab.ai/docs/api/find_places) | Coordinates of an address or place name, the address at a point, or a list of up to 100 addresses placed on a map with a CSV for QGIS. | | [Convert coordinates](https://terra-lab.ai/docs/api/convert_coordinates) | Coordinates converted between decimal degrees, degrees minutes seconds, UTM, MGRS, Lambert 93 or any EPSG code, with the format detected for you. | | [Measure distance, area, radius or buffer](https://terra-lab.ai/docs/api/measure) | Geodesic distance and bearing between places, the length of a line, the area and perimeter of a polygon, or a circle or buffer around a shape. | ### Map data | Tool | What it gives | | --- | --- | | [Extract open map data for an area](https://terra-lab.ai/docs/api/extract_open_data) | OpenStreetMap and Overture layers for any area: buildings, roads, addresses, points of interest, land use, water and more, as GIS files. | | [Open, map and convert GIS data](https://terra-lab.ai/docs/api/open_gis_file) | Any GeoJSON, KML, KMZ, GPX, Shapefile, CSV or WKT read, checked, shown on a map and converted to another format. | | [Buffer, merge, clip or simplify features](https://terra-lab.ai/docs/api/process_geometry) | One GIS operation on a set of features: buffer, union, dissolve, intersection, difference, clip or simplify, returned as a file. | ### Terrain and routes | Tool | What it gives | | --- | --- | | [Elevation, profile and contour lines](https://terra-lab.ai/docs/api/get_elevation) | Elevation of points, a profile along a path, or a DEM, hillshade, slope and contour lines for an area, worldwide at 30 m. | | [Road route between places](https://terra-lab.ai/docs/api/get_route) | Driving, walking or cycling route through 2 to 25 places, with distance and travel time per leg and the route line. | | [Area reachable in a travel time](https://terra-lab.ai/docs/api/get_travel_area) | The area reachable from 1 to 10 places within a travel time or a distance on the road network, driving, walking or cycling (isochrones). | ### Imagery and land | Tool | What it gives | | --- | --- | | [Satellite image of an area](https://terra-lab.ai/docs/api/get_satellite_image) | A recent Sentinel-2 image of an area in true colour, false colour or NDVI, or an IGN aerial photo in France, as a GeoTIFF. | | [Forest type (natural, planted, plantation)](https://terra-lab.ai/docs/api/get_forest_type) | Whether a forest is natural, naturally regenerating, planted, a plantation or tree crops, mapped at 10 m for 2020. | | [Estimate the population of an area](https://terra-lab.ai/docs/api/estimate_population) | A modelled estimate of the people living in a circle, a bounding box, a travel area or an administrative boundary, from the GHS-POP 2025 grid. | ### France | Tool | What it gives | | --- | --- | | [French cadastral parcel and zoning](https://terra-lab.ai/docs/api/french_parcel) | The French cadastral parcel at an address or point, its reference and area, and the urban plan zone that applies there. | | [French building heights](https://terra-lab.ai/docs/api/french_building_heights) | Height in metres, floors, main use and year built of every building around an address or in a small area in France, from IGN BD TOPO. | | [Natural risks at an address or a point](https://terra-lab.ai/docs/api/get_natural_risks) | Natural risks at an address or point: flood zones, clay, seismic zone, cavities and commune risks in France, river flood depth elsewhere. | ### Data catalog | Tool | What it gives | | --- | --- | | [Find a dataset in the TerraLab catalogue](https://terra-lab.ai/docs/api/find_datasets) | Search the TerraLab catalogue of open datasets by need or place, with each licence, coverage, size and the call that gets it. | | [Extract a hosted dataset for an area](https://terra-lab.ai/docs/api/extract_dataset) | A hosted dataset from the TerraLab catalogue cut to an area: a GeoTIFF or a vector file with its licence, or only the statistics. | | [Ask TerraLab to add data it does not have](https://terra-lab.ai/docs/api/request_data) | Ask TerraLab to add geographic data the catalogue does not have yet, with what you need and up to five places where you need it. | ## OpenAPI Import [https://mcp.terra-lab.ai/v1/openapi.json](https://mcp.terra-lab.ai/v1/openapi.json) into Postman, Insomnia, Bruno or a client generator. It is built from the tool schemas the server runs, so it is never behind the server. Source: https://terra-lab.ai/docs/api · Updated 2026-10-27 --- # Find places and addresses > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Coordinates of an address or place name, the address at a point, or a list of up to 100 addresses placed on a map with a CSV for QGIS. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/find_places` | | MCP tool | `find_places` on `https://mcp.terra-lab.ai/mcp` | | Group | Places | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/find_places \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query":"12 rue de la Paix, Annecy"}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/find_places", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "query": "12 rue de la Paix, Annecy", }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/find_places', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "query": "12 rue de la Paix, Annecy" }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"latitude":48.8584,"longitude":2.2945} ``` ```json {"addresses":["10 rue de Rivoli, Paris","Empire State Building, New York"],"title":"Visits"} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `query` | string | No | One address or place name. The place the user asks about, not the user's own location. | | `latitude` | number | No | Latitude for a reverse lookup. The place the user asks about, not the user's own location. From -90 to 90. | | `longitude` | number | No | Longitude for a reverse lookup. The place the user asks about, not the user's own location. From -180 to 180. | | `limit` | integer | No | For a single query: maximum number of matches. From 1 to 10. Default `5`. | | `addresses` | array of string | No | A list of addresses to place, up to 100. The place the user asks about, not the user's own location. 1 to 100 items. | | `text` | string | No | A list as text: one address per line, or CSV with an address column. The place the user asks about, not the user's own location. | | `title` | string | No | For a list: map title, also used for file names. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user wants the coordinates (latitude and longitude, GPS position) of an address or place name, the address at given coordinates, or wants places or addresses put, pinned or plotted on an interactive map, including a whole list of addresses (up to 100, pasted or as CSV with an address column). Worldwide, OpenStreetMap data; French street addresses are refined with the national address base. Shows the points on a map; for a list, gives a CSV with latitude and longitude added (opens directly in QGIS as a delimited text layer), plus KML and GeoJSON. Not for weather, opening hours, reviews, driving or travel directions and times, property prices, or general knowledge about a place (history, facts, travel advice): answer those without this tool. Never use the user's current location unless they type it. Download links build their file when opened; no file is saved. Never pass these links to open_gis_file. Source: https://terra-lab.ai/docs/api/find_places · Updated 2026-10-27 --- # Convert coordinates > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Coordinates converted between decimal degrees, degrees minutes seconds, UTM, MGRS, Lambert 93 or any EPSG code, with the format detected for you. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/convert_coordinates` | | MCP tool | `convert_coordinates` on `https://mcp.terra-lab.ai/mcp` | | Group | Places | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/convert_coordinates \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"text":"48.8584, 2.2945\n45.8992 N 6.1294 E","target_epsg":"EPSG:2154"}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/convert_coordinates", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "text": "48.8584, 2.2945\n45.8992 N 6.1294 E", "target_epsg": "EPSG:2154", }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/convert_coordinates', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "text": "48.8584, 2.2945\n45.8992 N 6.1294 E", "target_epsg": "EPSG:2154" }), }); const data = await response.json(); console.log(data.structuredContent); ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `text` | string | Yes | One coordinate per line, up to 1,000 lines. A label may follow the numbers. | | `target_epsg` | string | No | Optional extra output system as an EPSG code. | | `source_epsg` | string | No | Optional system the input is in, as an EPSG code, when it cannot be detected: Dutch RD EPSG:28992, Swiss LV95 EPSG:2056, British National Grid EPSG:27700, Poland 1992 EPSG:2180, UTM zone 32N EPSG:32632, ETRS89 or ED50 UTM EPSG:25830 / EPSG:23030. Leave out to detect each line. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user gives coordinates and wants them in another system or format: decimal degrees, degrees minutes seconds (GPS), UTM, MGRS, Lambert 93 (EPSG:2154, the French grid) or an EPSG code, for example Lambert 93 to WGS84 latitude and longitude, or GPS coordinates to UTM. The format of each line is detected automatically, including "lat, lon" or "lon, lat"; for a national grid the numbers alone cannot reveal (Dutch RD, Swiss LV95, British National Grid, Polish 1992, Belgian Lambert 72) or a UTM zone given apart from the numbers, set source_epsg. Converts one point or up to 1,000 lines to decimal degrees, DMS, UTM and MGRS (1 m), and optionally to an EPSG code, shows them on a map, and recommends the local projected CRS for working in QGIS. Returns a CSV and a GeoJSON download (both open directly in QGIS). Needs actual coordinates: to explain what a CRS, a projection or an EPSG code is, or to write conversion code, answer without this tool. Download links build their file when opened; no file is saved. Never pass these links to open_gis_file. Source: https://terra-lab.ai/docs/api/convert_coordinates · Updated 2026-10-27 --- # Measure distance, area, radius or buffer > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Geodesic distance and bearing between places, the length of a line, the area and perimeter of a polygon, or a circle or buffer around a shape. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/measure` | | MCP tool | `measure` on `https://mcp.terra-lab.ai/mcp` | | Group | Places | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/measure \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"method":"distance","points":["Paris, France","Geneva, Switzerland"]}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/measure", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "method": "distance", "points": ["Paris, France", "Geneva, Switzerland"], }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/measure', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "method": "distance", "points": [ "Paris, France", "Geneva, Switzerland" ] }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"method":"radius","center":"Lyon Part-Dieu","radius_m":5000} ``` ```json {"method":"area","points":[{"latitude":45.9,"longitude":6.12},{"latitude":45.9,"longitude":6.13},{"latitude":45.91,"longitude":6.13}]} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `method` | string | Yes | What to measure. One of `"distance"`, `"area"`, `"radius"`, `"buffer"`. | | `geometry` | object | No | A drawn geometry as GeoJSON (Point, LineString, Polygon, MultiLineString or MultiPolygon, WGS 84 longitude, latitude), or a Feature holding one. | | `geometry.type` | string | No | | | `geometry.coordinates` | array | No | | | `geometry.geometry` | object | No | | | `formats` | array of string | No | Download formats. Default `["geojson","kml"]`. | | `points` | array of string or object | No | For distance (2+) and area (3+): points or place names in order. The place the user asks about, not the user's own location. 2 to 500 items. | | `center` | string or object | No | For radius: the center, a point or a place name. The place the user asks about, not the user's own location. | | `radius_m` | number | No | For radius and buffer: the distance in metres (convert km or miles to metres first). From 1 to 2000000. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user wants a straight-line (as the crow flies) distance between places with midpoint and bearing, the length of a drawn line, the area and perimeter of a polygon, a circle of a given radius around a place, or a buffer of a given width around a drawn line or polygon. Distances and areas are geodesic on the WGS 84 ellipsoid, buffers are built in a local equidistant projection; every figure states its unit (m, km, m², ha, km²). method "distance" takes 2 or more points or place names, or a line in geometry; "area" takes 3 or more points or a polygon in geometry; "radius" takes a center and radius_m; "buffer" takes a geometry and radius_m. Not a road or travel distance. Returns the numbers, a map and GeoJSON, KML, Shapefile zip or CSV downloads. Never use the user's current location unless they type it. Give the place as the user names it (place name, address or coordinates): this tool locates it, so find_places is not needed first. Download links build their file when opened; no file is saved. Never pass these links to open_gis_file: list every format the user needs in formats, in this one call. Source: https://terra-lab.ai/docs/api/measure · Updated 2026-10-27 --- # Extract open map data for an area > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. OpenStreetMap and Overture layers for any area: buildings, roads, addresses, points of interest, land use, water and more, as GIS files. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/extract_open_data` | | MCP tool | `extract_open_data` on `https://mcp.terra-lab.ai/mcp` | | Group | Map data | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/extract_open_data \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"theme":"buildings","area":{"bbox":[2.34,48.857,2.343,48.859]},"formats":["shapefile"]}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/extract_open_data", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "theme": "buildings", "area": { "bbox": [2.34, 48.857, 2.343, 48.859], }, "formats": ["shapefile"], }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/extract_open_data', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "theme": "buildings", "area": { "bbox": [ 2.34, 48.857, 2.343, 48.859 ] }, "formats": [ "shapefile" ] }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"theme":"pois","area":{"place":"Annecy","radius_m":500},"kind":"pharmacy"} ``` ```json {"theme":"divisions","area":{"place":"Bretagne, France"},"level":"county","formats":["shapefile"]} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `theme` | string | Yes | The data layer. One of `"buildings"`, `"places"`, `"roads"`, `"addresses"`, `"divisions"`, `"landuse"`, `"pois"`, `"waterways"`, `"water_areas"`, `"power"`, `"boundaries"`, `"railways"`, `"routes"`, `"transit_stops"`, `"protected_areas"`. | | `area` | object | Yes | The area: a bbox, or a place (name, address or coordinates) with an optional radius in metres. The place the user asks about, not the user's own location. | | `area.bbox` | array of number | No | [west, south, east, north] in decimal degrees (WGS 84). Exactly 4 items. | | `area.place` | string or object | No | A place name or address, or a point. | | `area.radius_m` | number | No | Radius around the place in metres. From 1 to 100000. | | `formats` | array of string | No | Download formats. A GeoPackage is always included. Default `["geojson"]`. | | `level` | string | No | For the divisions theme: the administrative level to keep. region = French région, US state, German Land; county = French département, US county; municipality = commune, town or city. With a named place, a division of that name at this level is returned alone, and lower levels are kept only inside the named place ("the departments of Brittany": place Brittany, level county). One of `"country"`, `"region"`, `"county"`, `"municipality"`. | | `kind` | string or array of string | No | Keep only some kinds of feature, matched against their OpenStreetMap tag value in English: pharmacy, school, supermarket, restaurant, hospital, farmland, forest, vineyard. One kind, or several in one call (["forest", "meadow", "grass"]). Leave out for all of the theme. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user wants map data or GIS layers for a place or an area anywhere in the world, such as the data of a city, a town, a neighbourhood or a site: buildings, roads, addresses, points of interest (shops, schools, pharmacies), land use, waterways, water areas, railways, transit stops, power lines, protected areas, administrative divisions and boundaries, places or routes. The area is a place, a place with a radius, or a bbox. Returns the count, sample attributes, a map and a GIS file (GeoJSON, KML, Shapefile zip or CSV) with its licence. One extract holds at most 20,000 features: for a town centre use a radius of about 1 km, for dense cities less. For one kind of place (pharmacies, schools, supermarkets) or land (forest, vineyards), set kind: the limit then counts that kind only, so a whole city usually fits. For administrative boundaries (a commune, the departments of a region, a country) use divisions with level and the place name, without a radius. One theme per call: extract the layers the user asks for; when they ask for the data of a place without naming a layer, or what data exists for a project, name the useful themes and let them choose before extracting several. Data: Overture Maps and OpenStreetMap. Never use the user's current location unless they type it. Give the place as the user names it (place name, address or coordinates): this tool locates it, so find_places is not needed first. Download links build their file when opened; no file is saved. Never pass these links to open_gis_file: list every format the user needs in formats, in this one call. Source: https://terra-lab.ai/docs/api/extract_open_data · Updated 2026-10-27 --- # Open, map and convert GIS data > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Any GeoJSON, KML, KMZ, GPX, Shapefile, CSV or WKT read, checked, shown on a map and converted to another format. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/open_gis_file` | | MCP tool | `open_gis_file` on `https://mcp.terra-lab.ai/mcp` | | Group | Map data | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/open_gis_file \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"text":"A6.12,45.9","convert_to":["gpx"]}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/open_gis_file", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "text": "A6.12,45.9", "convert_to": ["gpx"], }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/open_gis_file', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "text": "A6.12,45.9", "convert_to": [ "gpx" ] }), }); const data = await response.json(); console.log(data.structuredContent); ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `file` | object | No | A file uploaded in the conversation. | | `file.download_url` | string | Yes | | | `file.file_id` | string | Yes | | | `file.file_name` | string | No | | | `file.mime_type` | string | No | | | `url` | string | No | A public http(s) link to the file. | | `text` | string | No | The file content pasted as text (GeoJSON, KML, GPX, CSV or WKT). | | `points` | array of object | No | Points to show and convert, with a name. The places the user asks about, not the user's own location. 0 to 5000 items. | | `geojson` | object | No | A GeoJSON FeatureCollection, Feature or geometry in WGS 84, given as an object. | | `title` | string | No | Name for the map and the output files when there is no file name. | | `file_name` | string | No | Optional name of the file, used to name the outputs. | | `convert_to` | array of string | No | Output formats wanted. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user shares or links a geographic file (GeoJSON, KML, KMZ, GPX track, zipped Shapefile, CSV with coordinates, WKT), pastes its content, or has a list of points or GeoJSON to put on a map, and wants to see it, understand it (format, coordinate system, extent, feature count, geometry types, fields, problems) or convert it to GeoJSON, KML, KMZ, GPX, Shapefile zip or CSV, for example a GPX track to a Shapefile or a KML to GeoJSON. Shapefile, GeoJSON, KML and CSV outputs open directly in QGIS. Also covers long tracks or point lists too big to write in the chat. Not for fetching new data about a place: that is extract_open_data. Not for styling layers, print layouts or chaining steps on the layers of a QGIS project the user has open: TerraLab AI Agent in QGIS does that. Files up to 50 MB. Never use the user's current location unless they type it. Download links build their file when opened; no file is saved. TerraLab reads the file or link to answer and keeps no copy. Source: https://terra-lab.ai/docs/api/open_gis_file · Updated 2026-10-27 --- # Buffer, merge, clip or simplify features > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. One GIS operation on a set of features: buffer, union, dissolve, intersection, difference, clip or simplify, returned as a file. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/process_geometry` | | MCP tool | `process_geometry` on `https://mcp.terra-lab.ai/mcp` | | Group | Map data | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/process_geometry \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"operation":"buffer","geojson":{"type":"LineString","coordinates":[[6.12,45.9],[6.13,45.905]]},"radius_m":200}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/process_geometry", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "operation": "buffer", "geojson": { "type": "LineString", "coordinates": [[6.12, 45.9], [6.13, 45.905]], }, "radius_m": 200, }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/process_geometry', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "operation": "buffer", "geojson": { "type": "LineString", "coordinates": [ [ 6.12, 45.9 ], [ 6.13, 45.905 ] ] }, "radius_m": 200 }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"operation":"clip","url":"https://mcp.terra-lab.ai/dl/geojson/TOKEN/buildings.geojson","area":{"place":"Annecy","radius_m":150}} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `operation` | string | Yes | The operation to apply. One of `"buffer"`, `"union"`, `"intersection"`, `"difference"`, `"clip"`, `"simplify"`, `"centroid"`, `"area_length"`. | | `file` | object | No | A file uploaded in the conversation (GeoJSON, KML, KMZ, GPX, zipped Shapefile, CSV or WKT). | | `file.download_url` | string | Yes | | | `file.file_id` | string | Yes | | | `file.file_name` | string | No | | | `file.mime_type` | string | No | | | `url` | string | No | A public http(s) link to the features, or a TerraLab download link from an earlier answer. | | `geojson` | object | No | The features as a GeoJSON FeatureCollection, Feature or geometry in WGS 84 (longitude, latitude). | | `with_file` | object | No | For intersection, difference and clip: the second layer, uploaded in the conversation. | | `with_file.download_url` | string | Yes | | | `with_file.file_id` | string | Yes | | | `with_file.file_name` | string | No | | | `with_file.mime_type` | string | No | | | `with_url` | string | No | For intersection, difference and clip: a link to the second layer, or a TerraLab download link. | | `with_geojson` | object | No | For intersection, difference and clip: the second layer as GeoJSON in WGS 84. Only its polygons count. | | `area` | object | No | For clip: the area to keep, as a bbox or a place with a radius (its bounding box is used). The place the user asks about, not the user's own location. | | `area.bbox` | array of number | No | [west, south, east, north] in decimal degrees (WGS 84). Exactly 4 items. | | `area.place` | string or object | No | A place name or address, or a point. | | `area.radius_m` | number | No | Radius around the place in metres. From 1 to 100000. | | `radius_m` | number | No | For buffer: the distance in metres (convert km or miles first). From 0.1 to 100000. | | `tolerance_m` | number | No | For simplify: the largest allowed shift of the outline in metres. From 0.01 to 100000. | | `dissolve_field` | string | No | For union: dissolve into one feature per value of this field instead of one feature. | | `formats` | array of string | No | Download formats. All five by default. | | `title` | string | No | Name for the map and the output files. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user wants to apply one GIS operation to a set of features (an uploaded file, a link, inline GeoJSON, or a download link from another TerraLab answer) and get the result as a file: buffer every feature by radius_m, union (dissolve all features into one, or one per value of dissolve_field), intersection or difference with a second layer, clip to a second layer or to an area (place, place with radius_m, or bbox), simplify by tolerance_m, centroid of each feature, or area_length (adds area_m2, perimeter_m and length_m to each feature, geodesic). Overlays run in WGS 84 degrees; buffer, simplify and centroid in a local equidistant projection. Up to 20,000 features and 100,000 vertices (10,000 for buffer). Returns the feature count, geometry types, total area or length, a map and GeoJSON, KML, Shapefile zip, GeoPackage and CSV downloads. Not for one distance or the area of a shape drawn in the chat: that is measure. Not for reading or converting a file without changing its geometry: that is open_gis_file. Not for styling layers, print layouts or chaining steps on the layers of a QGIS project the user has open: TerraLab AI Agent in QGIS does that. Never use the user's current location unless they type it. Download links build their file when opened; no file is saved. Never pass these links to open_gis_file: list every format the user needs in formats, in this one call. TerraLab reads the file or link to answer and keeps no copy. Source: https://terra-lab.ai/docs/api/process_geometry · Updated 2026-10-27 --- # Elevation, profile and contour lines > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Elevation of points, a profile along a path, or a DEM, hillshade, slope and contour lines for an area, worldwide at 30 m. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/get_elevation` | | MCP tool | `get_elevation` on `https://mcp.terra-lab.ai/mcp` | | Group | Terrain and routes | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/get_elevation \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"mode":"point","points":["Annecy, France"]}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/get_elevation", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "mode": "point", "points": ["Annecy, France"], }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/get_elevation', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "mode": "point", "points": [ "Annecy, France" ] }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"mode":"path","points":["Chamonix, France",{"latitude":45.8786,"longitude":6.8873,"name":"Aiguille du Midi"}]} ``` ```json {"mode":"area","area":{"place":"Chamonix, France","radius_m":2000},"interval_m":50} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `mode` | string | No | point: elevation of each point; path: profile along the points; area: DEM, hillshade and slope GeoTIFFs, plus contour lines and slope statistics for small areas. One of `"point"`, `"path"`, `"area"`. Default `"point"`. | | `points` | array of string or object | No | For point and path: points or place names. The place the user asks about, not the user's own location. 1 to 100 items. | | `samples` | integer | No | For path: number of samples along the path. From 10 to 500. Default `200`. | | `area` | object | No | For area: The area: a bbox, or a place (name, address or coordinates) with an optional radius in metres. The place the user asks about, not the user's own location. | | `area.bbox` | array of number | No | [west, south, east, north] in decimal degrees (WGS 84). Exactly 4 items. | | `area.place` | string or object | No | A place name or address, or a point. | | `area.radius_m` | number | No | Radius around the place in metres. From 1 to 100000. | | `interval_m` | number | No | For area: vertical interval between contour lines, in metres. From 1 to 1000. Default `20`. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user asks how high a place is (altitude, height above sea level), wants an elevation profile or the climb between places (a hike, a ride or a road up a mountain: elevation gain and loss, highest and lowest points, cross-section), or asks about the terrain of an area, anywhere in the world: a DEM, relief or hillshade GIS file, contour lines (topographic lines) and how steep or flat it is (slope). Place names work directly, for example a town and a summit. Reads the values from Copernicus DEM, a 30 m surface model of the whole world, and returns the numbers, a profile chart, the path or contours on a map and files. mode "point": up to 100 points. mode "path": 2 or more points, sampled into at most 500 samples. mode "area": a bbox, a place or a place with a radius, of any size: returns the DEM clipped to the area as a GeoTIFF (30 m, coarser for very large areas), a hillshade GeoTIFF and a slope GeoTIFF, which open directly in QGIS; for areas up to about 10 x 10 km it also returns contour lines (interval_m between lines; Shapefile, GeoJSON, KML) and slope statistics, so one call answers how steep a site is. Not for routes or travel times. Needs a place, coordinates or an area: to explain what a DEM, a DSM or contour lines are, answer without this tool. Never use the user's current location unless they type it. Give the place as the user names it (place name, address or coordinates): this tool locates it, so find_places is not needed first. Download links build their file when opened; no file is saved. Never pass these links to open_gis_file. Source: https://terra-lab.ai/docs/api/get_elevation · Updated 2026-10-27 --- # Road route between places > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Driving, walking or cycling route through 2 to 25 places, with distance and travel time per leg and the route line. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/get_route` | | MCP tool | `get_route` on `https://mcp.terra-lab.ai/mcp` | | Group | Terrain and routes | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/get_route \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"points":["Annecy, France","Chamonix-Mont-Blanc, France"],"mode":"driving"}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/get_route", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "points": ["Annecy, France", "Chamonix-Mont-Blanc, France"], "mode": "driving", }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/get_route', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "points": [ "Annecy, France", "Chamonix-Mont-Blanc, France" ], "mode": "driving" }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"points":["Lyon Part-Dieu","Vieux Lyon","Croix-Rousse, Lyon"],"mode":"walking"} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `points` | array of string or object | Yes | Start, optional stops and destination, in travel order: place names or {latitude, longitude}. 2 to 25 items. | | `mode` | string | No | How the route is travelled. One of `"driving"`, `"walking"`, `"cycling"`. Default `"driving"`. | | `avoid_tolls` | boolean | No | Driving: avoid toll roads where a reasonable alternative exists. Default `false`. | | `avoid_highways` | boolean | No | Driving or cycling: avoid motorways and highways where possible. Default `false`. | | `avoid_ferries` | boolean | No | Avoid ferries where possible. Default `false`. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user wants the driving, walking or cycling route between 2 to 25 places or points in order: the distance and travel time along the roads and paths, per leg and in total, and the route line. Computed by Valhalla on the TerraLab server from the OpenStreetMap road network, with each road's typical speed. Options avoid tolls, highways or ferries. Returns the figures (distance_m, distance_km, duration_s, duration_min), a map of the line and GeoJSON, KML, GPX, Shapefile zip and GeoPackage downloads. Not for a straight-line distance (measure_distance or measure), live traffic, departure-time travel times, public transport or turn-by-turn navigation. Never use the user's current location unless they type it. Download links build their file when opened; no file is saved. Never pass these links to open_gis_file. Source: https://terra-lab.ai/docs/api/get_route · Updated 2026-10-27 --- # Area reachable in a travel time > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. The area reachable from 1 to 10 places within a travel time or a distance on the road network, driving, walking or cycling (isochrones). | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/get_travel_area` | | MCP tool | `get_travel_area` on `https://mcp.terra-lab.ai/mcp` | | Group | Terrain and routes | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/get_travel_area \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"starts":["Gare d'\''Annecy"],"minutes":[15,30],"mode":"walking"}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/get_travel_area", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "starts": ["Gare d'Annecy"], "minutes": [15, 30], "mode": "walking", }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/get_travel_area', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "starts": [ "Gare d'Annecy" ], "minutes": [ 15, 30 ], "mode": "walking" }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"starts":["Lyon Part-Dieu","Grenoble"],"km":[10,25],"mode":"driving"} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `starts` | array of string or object | Yes | Start places or points: place names or {latitude, longitude}. 1 to 10 items. | | `minutes` | array of number | No | Travel times in minutes, one polygon each. Leave out when km is given. 0 to 4 items. Default `[5,10,20]`. | | `km` | array of number | No | Distances along the roads in km, instead of minutes. 0 to 4 items. | | `mode` | string | No | How the area is travelled. One of `"driving"`, `"walking"`, `"cycling"`. Default `"driving"`. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user wants the area reachable from 1 to 10 places within a travel time or a distance along the roads (an isochrone, catchment or service area): for example everything within 5, 10 and 20 minutes on foot from a station, or 30 km by car. Driving, walking or cycling, computed by Valhalla on the TerraLab server from the OpenStreetMap road network with each road's typical speed. Returns one polygon per start and value, with fields start, minutes or km, and mode, a map, and GeoJSON, KML, Shapefile zip and GeoPackage downloads. Up to 4 values, at most 120 minutes or 200 km. Not for a circle of a fixed radius (circle_around_place or measure), live traffic, a given departure time or public transport. Never use the user's current location unless they type it. Download links build their file when opened; no file is saved. Never pass these links to open_gis_file. Source: https://terra-lab.ai/docs/api/get_travel_area · Updated 2026-10-27 --- # Satellite image of an area > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. A recent Sentinel-2 image of an area in true colour, false colour or NDVI, or an IGN aerial photo in France, as a GeoTIFF. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/get_satellite_image` | | MCP tool | `get_satellite_image` on `https://mcp.terra-lab.ai/mcp` | | Group | Imagery and land | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/get_satellite_image \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"area":{"place":"Annecy, France","radius_m":300}}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/get_satellite_image", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "area": { "place": "Annecy, France", "radius_m": 300, }, }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/get_satellite_image', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "area": { "place": "Annecy, France", "radius_m": 300 } }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"area":{"place":"Annecy, France","radius_m":2000},"source":"sentinel2","date_from":"2026-09-01","date_to":"2026-09-30"} ``` ```json {"area":{"place":"Beauce, France","radius_m":3000},"product":"ndvi"} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `area` | object | Yes | The area: a bbox, or a place (name, address or coordinates) with an optional radius in metres. The place the user asks about, not the user's own location. | | `area.bbox` | array of number | No | [west, south, east, north] in decimal degrees (WGS 84). Exactly 4 items. | | `area.place` | string or object | No | A place name or address, or a point. | | `area.radius_m` | number | No | Radius around the place in metres. From 1 to 100000. | | `source` | string | No | ign_ortho: IGN orthophotos, France only, true colour. sentinel2: Copernicus Sentinel-2, worldwide, 10 m. Default: ign_ortho inside metropolitan France for true colour without a date, sentinel2 otherwise. One of `"sentinel2"`, `"ign_ortho"`. | | `product` | string | No | Sentinel-2 only. true_color; false_color (B08, B04, B03: vegetation in red); ndvi (float32, NaN where cloud, shadow, snow or no data). One of `"true_color"`, `"false_color"`, `"ndvi"`. Default `"true_color"`. | | `date` | string | No | Sentinel-2: one day, YYYY-MM-DD. | | `date_from` | string | No | Sentinel-2: start of the date range, YYYY-MM-DD. Default: 60 days before date_to. | | `date_to` | string | No | Sentinel-2: end of the date range, YYYY-MM-DD. Default: today. | | `max_cloud_percent` | number | No | Sentinel-2: highest cloud cover of the whole scene, in percent. From 0 to 100. Default `20`. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user wants a satellite image or aerial photo of an area as a GIS file: a recent true colour picture, a false colour image, or NDVI to see vegetation, crops or forest health. The area is a bbox, a place, or a place with a radius. Two sources: IGN orthophotos (France only, about 0.5 to 1 m pixels, areas up to 2 x 2 km, radius_m 900 or less; the default inside metropolitan France for true colour) and Copernicus Sentinel-2 L2A (worldwide, 10 m, a scene every few days, areas up to 20 x 20 km, radius_m 9,500 or less; the default elsewhere and for false colour, NDVI or a date). For Sentinel-2 it picks the least cloudy scene covering the whole area in the date range (default: the last 60 days, at most 20 % cloud) and says its date, id and cloud cover. Returns a GeoTIFF in the source's own projected CRS (UTM or Lambert-93) with the licence in the file, and a preview on a map. Not for street-level photos, live weather or a picture more recent than a few days; to explain what NDVI or a band is, answer without this tool. Never use the user's current location unless they type it. Download links build their file when opened; no file is saved. Never pass these links to open_gis_file. Source: https://terra-lab.ai/docs/api/get_satellite_image · Updated 2026-10-27 --- # Forest type (natural, planted, plantation) > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Whether a forest is natural, naturally regenerating, planted, a plantation or tree crops, mapped at 10 m for 2020. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/get_forest_type` | | MCP tool | `get_forest_type` on `https://mcp.terra-lab.ai/mcp` | | Group | Imagery and land | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/get_forest_type \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"area":{"place":"Morcenx, France","radius_m":3000}}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/get_forest_type", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "area": { "place": "Morcenx, France", "radius_m": 3000, }, }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/get_forest_type', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "area": { "place": "Morcenx, France", "radius_m": 3000 } }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"area":{"bbox":[-60.2,-2.6,-60.1,-2.5]}} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `area` | object | Yes | The area: a bbox, or a place (name, address or coordinates) with an optional radius in metres. The place the user asks about, not the user's own location. | | `area.bbox` | array of number | No | [west, south, east, north] in decimal degrees (WGS 84). Exactly 4 items. | | `area.place` | string or object | No | A place name or address, or a point. | | `area.radius_m` | number | No | Radius around the place in metres. From 1 to 100000. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user asks whether a forest is natural, primary, naturally regenerating, planted, a plantation or tree crops (oil palm, cocoa, coffee, rubber, orchards, agroforestry), for example for EUDR due diligence (deforestation-free sourcing, 2020 cut-off), certification, a carbon or biodiversity project or a land purchase. The area is a bbox, a place, or a place with a radius (default 1,000 m), at most about 300 km². Reads Forest Typology (ForTy) 2020 v1 by Google DeepMind, a 10 m global map, and returns the share of each class in percent and hectares (primary forest, naturally regenerating forest, planted forest, plantation forest, tree crops and agroforestry, other land), the dominant class, a map, and a GeoTIFF of the class raster clipped to the area that opens in QGIS. Caveat: 2020 baseline; planted vs naturally regenerating is the least reliable class. Not for forest loss after 2020, tree height or species. Never use the user's current location unless they type it. Download links build their file when opened; no file is saved. Never pass these links to open_gis_file. Source: https://terra-lab.ai/docs/api/get_forest_type · Updated 2026-10-27 --- # Estimate the population of an area > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. A modelled estimate of the people living in a circle, a bounding box, a travel area or an administrative boundary, from the GHS-POP 2025 grid. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/estimate_population` | | MCP tool | `estimate_population` on `https://mcp.terra-lab.ai/mcp` | | Group | Imagery and land | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/estimate_population \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"area":{"place":"Gare de Lyon Part-Dieu","radius_m":1000}}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/estimate_population", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "area": { "place": "Gare de Lyon Part-Dieu", "radius_m": 1000, }, }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/estimate_population', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "area": { "place": "Gare de Lyon Part-Dieu", "radius_m": 1000 } }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"travel":{"start":"Nairobi Central railway station","minutes":15,"mode":"walk"}} ``` ```json {"area":{"place":"Villeurbanne","level":"municipality"}} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `area` | object | No | The area: a bbox, or a place (name, address or coordinates) with an optional radius in metres. The place the user asks about, not the user's own location. | | `area.bbox` | array of number | No | [west, south, east, north] in decimal degrees (WGS 84). Exactly 4 items. | | `area.place` | string or object | No | A place name or address, or a point. | | `area.radius_m` | number | No | Radius around the place in metres. From 1 to 100000. | | `area.level` | string | No | With a place and no radius: use its administrative boundary at this level (Overture divisions). One of `"country"`, `"region"`, `"county"`, `"municipality"`. | | `travel` | object | No | The area reachable from a start along the roads (Valhalla on the OpenStreetMap network), instead of area. | | `travel.start` | string or object | No | | | `travel.minutes` | number | No | At most 120. | | `travel.km` | number | No | At most 200. | | `travel.mode` | string | No | One of `"walk"`, `"drive"`, `"bike"`. Default `"walk"`. | | `source` | string | No | ghs_pop: GHS-POP 2025, 100 m. worldpop_1km: WorldPop, 1 km, to compare. One of `"ghs_pop"`, `"worldpop_1km"`. Default `"ghs_pop"`. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user asks how many people live in an area: "how many people live within 1 km of Lyon Part-Dieu?", "within 15 minutes' walk of Nairobi Central station", "population of the commune of Villeurbanne". Give exactly one of: area with a place and radius_m (a circle) or a bbox; area with a place and level (municipality, county, region, country) for its administrative boundary; or travel (start, minutes or km, mode walk, drive or bike) for the area reachable along the roads. Sums the GHS-POP 2025 grid (100 m cells, EC JRC, CC BY 4.0) over the polygon, or worldpop_1km with source. Returns the population rounded to 10, the density per km², the area, and the polygon used as a GeoPackage. This is a modelled estimate from satellite-derived built-up areas and census totals, not a census count: say so, and never present it as an official figure. Limits: 50,000 km² per call; travel at most 120 minutes or 200 km. Never use the user's current location unless they type it. Download links build their file when opened; no file is saved. Never pass these links to open_gis_file. Source: https://terra-lab.ai/docs/api/estimate_population · Updated 2026-10-27 --- # French cadastral parcel and zoning > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. The French cadastral parcel at an address or point, its reference and area, and the urban plan zone that applies there. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/french_parcel` | | MCP tool | `french_parcel` on `https://mcp.terra-lab.ai/mcp` | | Group | France | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/french_parcel \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"address":"12 rue de la Paix, Annecy"}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/french_parcel", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "address": "12 rue de la Paix, Annecy", }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/french_parcel', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "address": "12 rue de la Paix, Annecy" }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"latitude":48.8566,"longitude":2.3522} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `address` | string | No | An address in France. The place the user asks about, not the user's own location. | | `latitude` | number | No | Latitude in France. The place the user asks about, not the user's own location. From -90 to 90. | | `longitude` | number | No | Longitude in France. The place the user asks about, not the user's own location. From -180 to 180. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user asks which cadastral parcel (parcelle cadastrale, plot of land) is at an address or point in France, its reference (commune, section, number) and cadastral area, or which local urban plan zone applies there (PLU or PLUi zoning: zone label, type such as U, AU, A or N, and the plan document), for example before buying land or a house or before a building project. Reads the open data of the French cadastre (DGFiP) and of the Géoportail de l'Urbanisme. Shows the parcel on a map with GeoJSON and KML downloads. France only. Never returns owners or property sales. The zone is not a building permit decision. Never use the user's current location unless they type it. Give the place as the user names it (place name, address or coordinates): this tool locates it, so find_places is not needed first. Download links build their file when opened; no file is saved. Never pass these links to open_gis_file. Source: https://terra-lab.ai/docs/api/french_parcel · Updated 2026-10-27 --- # French building heights > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Height in metres, floors, main use and year built of every building around an address or in a small area in France, from IGN BD TOPO. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/french_building_heights` | | MCP tool | `french_building_heights` on `https://mcp.terra-lab.ai/mcp` | | Group | France | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/french_building_heights \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"area":{"place":"10 place Bellecour, Lyon","radius_m":150}}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/french_building_heights", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "area": { "place": "10 place Bellecour, Lyon", "radius_m": 150, }, }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/french_building_heights', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "area": { "place": "10 place Bellecour, Lyon", "radius_m": 150 } }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"area":{"bbox":[2.294,48.857,2.299,48.86]},"formats":["shapefile"]} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `area` | object | Yes | The area: a bbox, or a place (name, address or coordinates) with an optional radius in metres. The place the user asks about, not the user's own location. | | `area.bbox` | array of number | No | [west, south, east, north] in decimal degrees (WGS 84). Exactly 4 items. | | `area.place` | string or object | No | A place name or address, or a point. | | `area.radius_m` | number | No | Radius around the place in metres. From 1 to 100000. | | `formats` | array of string | No | Download formats. Default `["geojson"]`. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user asks how tall the buildings are around an address or in a small area in France: height in metres per building, floors, main use, year built and ground altitude, from IGN BD TOPO. The area is a place with a radius (default 150 m) or a bbox, at most 0.6 km². Shows the buildings on a map with a height summary (count, median, tallest) and GeoJSON, KML, Shapefile zip or CSV downloads. France only (mainland, Corsica and overseas departments). Never use the user's current location unless they type it. Give the place as the user names it (place name, address or coordinates): this tool locates it, so find_places is not needed first. Download links build their file when opened; no file is saved. Never pass these links to open_gis_file: list every format the user needs in formats, in this one call. Source: https://terra-lab.ai/docs/api/french_building_heights · Updated 2026-10-27 --- # Natural risks at an address or a point > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Natural risks at an address or point: flood zones, clay, seismic zone, cavities and commune risks in France, river flood depth elsewhere. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/get_natural_risks` | | MCP tool | `get_natural_risks` on `https://mcp.terra-lab.ai/mcp` | | Group | France | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/get_natural_risks \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"address":"12 quai Saint-Antoine, 69002 Lyon"}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/get_natural_risks", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "address": "12 quai Saint-Antoine, 69002 Lyon", }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/get_natural_risks', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "address": "12 quai Saint-Antoine, 69002 Lyon" }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"latitude":24.45,"longitude":89.7,"risks":["flood"]} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `address` | string | No | An address or place. The place the user asks about, not the user's own location. Up to 300 characters. | | `latitude` | number | No | From -90 to 90. | | `longitude` | number | No | From -180 to 180. | | `risks` | array of string | No | Keep only these risks. Leave out for all. 0 to 8 items. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user asks about the natural risks of an address or a point: "is 12 quai Saint-Antoine, Lyon in a flood zone?", "clay shrink-swell hazard at this address", "100-year flood depth at 24.45, 89.70 in Sirajganj, Bangladesh". Give exactly one of: address, or latitude with longitude. In France (TerraLab copies of Géorisques files, refreshed weekly): flood zones of the 124 TRI, clay shrink-swell exposure, seismic zone and cavities within 500 m at the point; flood, radon, landslide, wildfire and industrial risks of the commune (scope says point or commune). Elsewhere: JRC river flood depth for 10, 100 and 500-year floods (about 1 km cells). risks keeps some of them. Each value has a status: ok, absent (the source maps this place and finds nothing), failed (the source did not answer: say it is unknown and can be retried, never that there is no risk) or not_mapped (no source covers it here: unknown, not safe). Read the status before the value. One point per call. Regulatory zones are not a building decision; give the Géorisques report link for France. Never use the user's current location unless they type it. Source: https://terra-lab.ai/docs/api/get_natural_risks · Updated 2026-10-27 --- # Find a dataset in the TerraLab catalogue > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Search the TerraLab catalogue of open datasets by need or place, with each licence, coverage, size and the call that gets it. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/find_datasets` | | MCP tool | `find_datasets` on `https://mcp.terra-lab.ai/mcp` | | Group | Data catalog | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/find_datasets \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query":"land cover","area":{"place":"Kenya"}}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/find_datasets", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "query": "land cover", "area": { "place": "Kenya", }, }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/find_datasets', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "query": "land cover", "area": { "place": "Kenya" } }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"query":"administrative boundaries","area":{"place":"Indonesia"}} ``` ```json {"ids":["esa_worldcover"],"area":{"place":"Belgium"}} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `query` | string | No | What the data is about, in a few words: "land cover", "flood hazard", "administrative boundaries", "population". Up to 200 characters. | | `area` | object | No | The area: a bbox, or a place (name, address or coordinates) with an optional radius in metres. The place the user asks about, not the user's own location. | | `area.bbox` | array of number | No | [west, south, east, north] in decimal degrees (WGS 84). Exactly 4 items. | | `area.place` | string or object | No | A place name or address, or a point. | | `area.radius_m` | number | No | Radius around the place in metres. From 1 to 100000. | | `kind` | string | No | Keep one kind of dataset only. One of `"air_quality"`, `"basemap"`, `"biodiversity"`, `"boundaries"`, `"buildings"`, `"cadastre"`, `"climate"`, `"elevation"`, `"energy"`, `"extract"`, `"geology"`, `"hazards"`, `"health"`, `"hydrology"`, `"imagery"`, `"landcover"`, `"poi"`, `"population"`, `"settlements"`, `"soil"`, `"topographic_base"`, `"transport"`. | | `ids` | array of string | No | Describe these dataset ids (from an earlier answer) instead of searching; with area, each gets its size estimate there. 0 to 20 items. | | `limit` | integer | No | How many datasets to return. From 1 to 20. Default `5`. | | `request_id` | string | No | An id request_data returned (dr_ and 10 characters): gives that data request's status instead of searching. Up to 40 characters. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user asks what data exists for a need or a place before getting it: "what land cover data do you have for Kenya?", "official district boundaries of Indonesia", "is there a flood hazard map for Romania?". Searches the TerraLab catalogue of 344 hosted open datasets (land cover, boundaries, hazards, population, elevation, soil, buildings, hydrology, climate) and about 2,800 datasets TerraLab only points to. Give query (a few words), and area (a country or a place) to keep only datasets covering it and to get a size estimate. Returns at most 20 datasets (5 by default), best first, each with its licence and attribution quoted from the catalogue, resolution, date, coverage, whether TerraLab hosts it, and the call that gets it (extract_dataset, extract_open_data or get_elevation). Read hosted first: hosted false means TerraLab does not serve that data, so give its page and offer request_data; never present it as available. An empty list means the catalogue has nothing for this need here, not that the data does not exist. Not for OpenStreetMap features of a place (use extract_open_data). Never use the user's current location unless they type it. Source: https://terra-lab.ai/docs/api/find_datasets · Updated 2026-10-27 --- # Extract a hosted dataset for an area > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. A hosted dataset from the TerraLab catalogue cut to an area: a GeoTIFF or a vector file with its licence, or only the statistics. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/extract_dataset` | | MCP tool | `extract_dataset` on `https://mcp.terra-lab.ai/mcp` | | Group | Data catalog | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/extract_dataset \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"dataset":"esa_worldcover","area":{"place":"Nakuru, Kenya","radius_m":5000}}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/extract_dataset", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "dataset": "esa_worldcover", "area": { "place": "Nakuru, Kenya", "radius_m": 5000, }, }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/extract_dataset', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "dataset": "esa_worldcover", "area": { "place": "Nakuru, Kenya", "radius_m": 5000 } }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"dataset":"esa_worldcover","area":{"place":"Gare de Montpellier Saint-Roch","radius_m":2000},"summary":true} ``` ```json {"dataset":"cod_ab_idn","area":{"place":"Bandung, Indonesia","radius_m":10000},"variables":{"LEVEL":"3"}} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `dataset` | string | No | A dataset id from find_datasets, such as "esa_worldcover", "ghs_pop", "cod_ab_idn". Up to 80 characters. | | `area` | object | No | The area: a bbox, or a place (name, address or coordinates) with an optional radius in metres. The place the user asks about, not the user's own location. | | `area.bbox` | array of number | No | [west, south, east, north] in decimal degrees (WGS 84). Exactly 4 items. | | `area.place` | string or object | No | A place name or address, or a point. | | `area.radius_m` | number | No | Radius around the place in metres. From 1 to 100000. | | `variables` | object | No | Values for the dataset's variables, as find_datasets lists them: {"LEVEL": "2"}, {"RETURN_PERIOD": "100"}, {"YEAR": "2015"}. | | `summary` | boolean | No | Rasters: return the class shares or statistics only, without a file. Default `false`. | | `formats` | array of string | No | Vectors: extra download formats. A GeoPackage is always included. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user wants a dataset from the TerraLab catalogue cut to an area, as a GIS file or as numbers: "ESA WorldCover for 5 km around Nakuru as a GeoTIFF". Give dataset (an id from find_datasets) and area (a place with radius_m, or a bbox). Rasters: a GeoTIFF on the dataset's own grid, licence in the file and in a zip, plus class shares or min, max and mean (summary true: numbers only). Vectors: a GeoPackage (GeoJSON, KML, Shapefile or CSV on request) with the feature count. Limits: 4,000 x 4,000 pixels (8 million for 64-bit data) or 20,000 features per call; a larger area gets area_too_large with a radius that fits, never a partial file. Some datasets need variables (admin level, year, return period): find_datasets lists them. Quote the licence and attribution in the answer. Not for OpenStreetMap or Overture (extract_open_data), elevation (get_elevation) or satellite images (get_satellite_image). Never use the user's current location unless they type it. Download links build their file when opened; no file is saved. Never pass these links to open_gis_file: list every format the user needs in formats, in this one call. Source: https://terra-lab.ai/docs/api/extract_dataset · Updated 2026-10-27 --- # Ask TerraLab to add data it does not have > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Ask TerraLab to add geographic data the catalogue does not have yet, with what you need and up to five places where you need it. | | | | --- | --- | | REST | `POST https://mcp.terra-lab.ai/v1/tools/request_data` | | MCP tool | `request_data` on `https://mcp.terra-lab.ai/mcp` | | Group | Data catalog | ## Request ```bash curl -X POST https://mcp.terra-lab.ai/v1/tools/request_data \ -H "Authorization: Bearer $TERRALAB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"description":"Official flood zones of Morocco","example_locations":["Fes, Morocco"]}' ``` ```python import os import requests response = requests.post( "https://mcp.terra-lab.ai/v1/tools/request_data", headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"}, json={ "description": "Official flood zones of Morocco", "example_locations": ["Fes, Morocco"], }, timeout=120, ) response.raise_for_status() print(response.json()["structuredContent"]) ``` ```javascript const response = await fetch('https://mcp.terra-lab.ai/v1/tools/request_data', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "description": "Official flood zones of Morocco", "example_locations": [ "Fes, Morocco" ] }), }); const data = await response.json(); console.log(data.structuredContent); ``` Other valid request bodies: ```json {"description":"Noise maps of Lyon","example_locations":["Lyon"],"idempotency_key":"noise-lyon-1"} ``` ## Parameters A JSON object in the request body; in an MCP client, the arguments of the tool call. | Name | Type | Required | Description | | --- | --- | --- | --- | | `description` | string | No | The data needed, in the user's words: "official flood zones of Morocco". Up to 2000 characters. | | `example_locations` | array of string or object | No | Up to 5 places where the data is needed: each a place name, {"place": name}, or {"latitude", "longitude"}; never both a place and coordinates. 0 to 5 items. | | `use_case` | string | No | What the user wants to do with it, when they said so. Up to 500 characters. | | `file_anyway` | boolean | No | File the request even when a hosted dataset seems to answer it (the user said it does not). Default `false`. | | `idempotency_key` | string | No | Any string that stays the same if this call is sent again; the same key and description return the first request. Up to 80 characters. | ## Response `structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change. ```json { "structuredContent": { "sources": [{ "name": "...", "licence": "...", "url": "..." }], "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }] }, "content": [{ "type": "text", "text": "..." }], "isError": false } ``` Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened. ## Errors A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors). ## Description for agents The text the server gives an agent to decide when to call this tool: > Use this when the user needs geographic data that find_datasets did not find, and wants TerraLab to add it: "you don't have the official flood zones of Morocco? Can you ask TerraLab to add them?". Give description (what data, in the user's words) and up to 5 example_locations (places or coordinates where it is needed). The catalogue is searched first: when a hosted dataset already answers, the answer is matched_existing with the call that gets it and nothing is filed (file_anyway true files it all the same, for data the user says is different). Otherwise the request is filed and gets a request_id (dr_ and 10 characters) to quote to the user; find_datasets with request_id gives its status later. Someone at TerraLab reads every request once a week; no date is promised, so never promise one. The same idempotency_key with the same description returns the first request instead of filing it twice. Only for data TerraLab could host (maps, layers, statistics by place), not for questions or bugs. Never use the user's current location unless they type it. Source: https://terra-lab.ai/docs/api/request_data · Updated 2026-10-27 --- # Pricing and free calls > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Pay only for what you use. The MCP server and the REST API share one grid and one balance per account. | What | Price | | --- | --- | | Calls included every month | 100, at no cost | | Calls after that | 5 per 1,000 calls | | Satellite image | 0.05 per image | Amounts are the same figure in EUR and USD, excluding VAT. EUR applies where VAT is added (the European Union), USD elsewhere. ## How it works - The included calls reset on the 1st of each month at 00:00 UTC. - Past them, each call is taken from your prepaid balance. A satellite image is taken from the balance even within the included calls. - Top up from [Dashboard, MCP](https://terra-lab.ai/dashboard/mcp) by card: 10, 25, 50, 100. The balance does not expire. - No subscription and no commitment. Within 14 days of a top-up, you can withdraw from it and get back what is left unused. - A call that fails or finds nothing costs nothing. ## Read the grid from code The same grid as JSON, with no key needed: ```bash curl https://mcp.terra-lab.ai/v1/meta/pricing ``` Your own usage and balance come from [`GET /v1/me`](https://terra-lab.ai/docs/api#account-and-discovery). ## The QGIS plugins AI Agent, AI Segmentation and AI Edit have their own free tiers and plans, on the [pricing page](https://terra-lab.ai/pricing). See [QGIS plugins](https://terra-lab.ai/docs/plugins). Source: https://terra-lab.ai/docs/pricing · Updated 2026-10-27 --- # Errors and rate limits > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Errors come as JSON with a stable `error` code and a `message` written for a person: ```json { "error": "auth_required", "message": "This tool needs a TerraLab account. Connect TerraLab in your assistant's settings; see https://terra-lab.ai/mcp." } ``` Branch on `error`, never on `message`: the wording can change. ## HTTP errors | Status | `error` | Meaning | What to do | | --- | --- | --- | --- | | 400 | `invalid_input` | The body is not a JSON object of the tool arguments. | Fix the request body. | | 400 | `bad_request` | The request could not be read. | Check the JSON and the headers. | | 401 | `auth_required` | No key or token was sent. | Send `Authorization: Bearer `. | | 401 | `invalid_token` | The key or token is invalid, revoked or expired. | Create a new key, or let the agent sign in again. | | 401 | `access_revoked` | The app was disconnected from the dashboard. | Reconnect the agent. | | 402 | `balance_required` | The free calls of the month are used, or the operation is paid, and the balance is empty. | Add funds; the answer carries `hint` and `account_url`. | | 404 | `not_found` | There is no such API path. | Check the URL. | | 404 | `unknown_tool` | No tool has that name. | List tools with `GET /v1/tools`. | | 405 | `method_not_allowed` | Tools are called with `POST`. | Use `POST`. | | 413 | `too_large` | The request body is too large. | Send a smaller file or fewer features. | | 429 | `rate_limited` | Too many requests in the last minute. | Wait for `Retry-After` seconds, then retry. | | 429 | `daily_limit` | The account reached its daily safety ceiling. | Retry the next day (UTC) or write to support. | | 503 | `unavailable` | The account service did not answer. | Retry after `Retry-After` seconds. | ## Tool errors A tool that runs but cannot answer returns `200` with `isError: true`. Its `structuredContent` then carries `code`, `message` and, most of the time, a `hint` saying what to change: a place it could not find, an area too large, a country it does not cover. ```json { "structuredContent": { "code": "...", "message": "...", "hint": "..." }, "content": [{ "type": "text", "text": "..." }], "isError": true } ``` ## What is counted A call that fails or finds nothing is not counted: the free call or the amount taken from the balance is given back. Only answers with a result count. ## Rate limits Every account can make 60 calls a minute. Past it, the API answers `429` with `Retry-After: 60`. Spread batch jobs over time, and retry with a growing delay: ```python import time import requests def call(url, headers, body, attempts=5): for attempt in range(attempts): response = requests.post(url, headers=headers, json=body, timeout=120) if response.status_code not in (429, 503): return response time.sleep(int(response.headers.get("Retry-After", 2 ** attempt))) return response ``` Retry only `429` and `503`. A `401` or `402` will not pass on a second try. Source: https://terra-lab.ai/docs/errors · Updated 2026-10-27 --- # Data sources and licences > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. TerraLab serves open data. Every result lists its `sources` with their licence, and every file you download keeps that licence and its attribution. You pay for the service, never for the data. ## Main sources | Source | Used for | Licence | | --- | --- | --- | | OpenStreetMap | Roads, places, waterways, land use, transit, routes and travel-time areas | ODbL 1.0 | | Overture Maps | Buildings, places, addresses and boundaries | ODbL or CDLA Permissive 2.0, by theme | | Copernicus DEM | Elevation, profiles and contour lines, worldwide at 30 m | Copernicus DEM licence, free with attribution | | Copernicus Sentinel-2 | Satellite images worldwide at 10 m | Copernicus data, free with attribution | | IGN and DGFiP | French addresses, cadastral parcels, urban plan zones, building heights and aerial photos | Licence Ouverte 2.0 | | GHS-POP 2025 (European Commission, JRC) | Population estimates, 100 m grid | CC BY 4.0 | | ForTy 2020 (Google DeepMind, Google Research, WRI, IIASA) | Forest type at 10 m | CC BY 4.0, modified by TerraLab (clip, reprojection) | | Géorisques, BRGM, ASNR | French natural risks: flood zones, clay, radon, seismic zoning, cavities | Licence Ouverte 2.0 | | JRC global river flood hazard | Flood depth outside France | CC BY 4.0 | Hosted datasets from the TerraLab catalogue ([find_datasets](https://terra-lab.ai/docs/api/find_datasets), [extract_dataset](https://terra-lab.ai/docs/api/extract_dataset)) each keep their own licence, quoted with them. The full list of licences and the attribution to show is on [Data licences](https://terra-lab.ai/legal/data-licences). ## Attribution When you publish a map or a dataset built from a TerraLab result, show the credits from its `sources`. For OpenStreetMap data, that is "© OpenStreetMap contributors"; ODbL also asks that a database you share stays under ODbL. ## Limits - Up to 20,000 features per extract, about 1 km around a town centre. - Elevation at 30 m; contour areas up to about 10 × 10 km. - Files up to 50 MB, lists up to 100 addresses. - Parcels, zoning, building heights and IGN photos: France only. - Routes through 25 places; travel areas up to 120 min or 200 km. - Satellite images up to 20 × 20 km at 10 m. - Approximate: not for navigation, surveying or legal boundaries. Each tool page in the [API reference](https://terra-lab.ai/docs/api) gives the exact range of every parameter. ## Privacy - Only what the request needs is sent: a place, an area or a file. - Logs keep the tool, its status and duration, never your question. The requested area is kept 30 days, with no user identifier. - Files are processed in memory, never stored. - Never sold, never used to train AI. Read the [privacy policy](https://terra-lab.ai/privacy-policy) and the [API terms](https://terra-lab.ai/legal/api-terms). Source: https://terra-lab.ai/docs/data-sources · Updated 2026-10-27 --- # Support > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Write to [yvann.barbot@terra-lab.ai](mailto:yvann.barbot@terra-lab.ai). The founders read every message and answer within 2 working days. ## What to include - What you called: the tool name, or the agent and your question. - The request body, without your API key. - The answer you got: status code, `error` code and `message`, or the `structuredContent.code` of a tool error. - The time of the call, with its time zone. Never send an API key by email. If one leaked, revoke it on [Dashboard, MCP](https://terra-lab.ai/dashboard/mcp) and create a new one. ## A wrong or missing result Data comes from open sources and can be wrong or incomplete. Tell us the place and what you expected: we check the source and fix what is ours. To ask for a dataset TerraLab does not have yet, call [request_data](https://terra-lab.ai/docs/api/request_data) or write to us. ## Other pages - [Errors and rate limits](https://terra-lab.ai/docs/errors) explains every error code. - The [support page](https://terra-lab.ai/support) covers accounts, invoices and the QGIS plugins. - The [API terms](https://terra-lab.ai/legal/api-terms) and the [acceptable use policy](https://terra-lab.ai/legal/acceptable-use) set the rules of use. Source: https://terra-lab.ai/docs/support · Updated 2026-10-27 --- # Changelog > Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL. Changes to the MCP server and the REST API, newest first. Additive changes keep older clients working; anything that breaks a client is announced here first. ## 2026-10-27 - Developer documentation published at [terra-lab.ai/docs](https://terra-lab.ai/docs), with a markdown copy of every page and [/docs/llms.txt](https://terra-lab.ai/docs/llms.txt). - Tool descriptions state the `radius_m` limits of `get_satellite_image`, so agents get a valid call on the first try. - `HEAD` works on `/v1/openapi.json` and `/v1/meta/pricing`, for link checkers. ## 2026-10-26 - Out of free calls or balance, the `402` answer points to the account page in `account_url`. ## 2026-10-25 - New price grid: 100 calls included every month, then 5 per 1,000 calls and 0.05 per satellite image, from a prepaid balance. No subscription. See [Pricing](https://terra-lab.ai/docs/pricing). - A call that fails or finds nothing is no longer counted. - `GET /v1/me` returns the exact balance in `balance.micros`. - `GET /v1/meta/pricing` publishes the grid as JSON. - `get_natural_risks` reads a weekly mirror of the Géorisques files. ## 2026-10-24 - TerraLab accounts on the MCP server, with OAuth 2.1 sign-in for agents and `tlk_` API keys for code. - REST API at `https://mcp.terra-lab.ai/v1`, with an [OpenAPI 3.1 document](https://mcp.terra-lab.ai/v1/openapi.json). - New tools: `find_datasets`, `extract_dataset`, `request_data`, `estimate_population`, `get_forest_type` and `get_natural_risks`. - Sentinel-2 images are cached 90 days by area, scene and rendering. ## 2026-10-23 - New tools: `get_route`, `get_travel_area`, `process_geometry` and `get_satellite_image`. - `extract_open_data` accepts `geopackage` as a format. ## 2026-10-19 - TerraLab app live in ChatGPT. - `get_elevation` area mode returns DEM, hillshade and slope GeoTIFFs for any area. - Every open data extract also offers a GeoPackage. Source: https://terra-lab.ai/docs/changelog · Updated 2026-10-27