---
title: "Start a 3D building extract around an address or a WGS84 point"
description: "Backs the aireon hub '3D extract' admin page and the Grasshopper/API path. Geocodes the address (geo.admin SearchServer) - or, when `lat`+`lng` are given, takes that WGS84 point directly (LV95 via the swisstopo REFRAME service; label and EGID from the GWR building under the point, best effort) - finds the newest per-sheet swisstopo swissBUILDINGS3D 3.0 Beta tile via the data.geo.admin.ch STAC API (FileGDB, cached under cache/building3d/), clips all buildings intersecting the ±radius_m box around the point (whole buildings, not sliced; layers Building_solid + Roof_solid), builds an OBJ/MTL mesh (local origin at the point, Y-up) + a georeferenced DXF (3DFACE mesh on WALL/ROOF layers, absolute LV95/LN02 metres - opens in AutoCAD/BricsCAD/Rhino/SketchUp) + a DWG R2000 (same mesh, converted from the DXF via LibreDWG when available) + GeoPackage (EPSG:2056, full attributes incl. EGID and heights) + preview render + README, and zips it as `<slug>_3d_<radius>m.zip`.\n\nResponds 202 immediately with a `jobId`. Two ways to get the result: (1) give an `email` and the zip (or a failure report - unknown address, no tile covering the point, empty box) arrives by Resend in about a minute, exactly as before; (2) poll `statusUrl` until `status` is `ready`, then GET `downloadUrl` (the zip) or `downloadUrl/{file}` for one member. Without `email` no mail is sent at all. Outputs are kept under cache/building3d/jobs/<jobId>/ and evicted least-recently-used once the jobs directory exceeds its cap (default 5 GiB, `BUILDING3D_JOBS_MAX_BYTES`); the download endpoints touch a job on every fetch, so a result that keeps being fetched stays.\n\nConcurrency: at most 3 extractions per pm2 worker (RES runs 2 cluster workers, so 6 box-wide); beyond that the request is answered 429 and should simply be retried a minute later."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.aireon.ch/llms.txt
> Use this file to discover all available pages before exploring further.

Path: RES API (public) › Building3D

`POST /res_api/building3d-extract`

Backs the aireon hub '3D extract' admin page and the Grasshopper/API path. Geocodes the address (geo.admin SearchServer) - or, when `lat`+`lng` are given, takes that WGS84 point directly (LV95 via the swisstopo REFRAME service; label and EGID from the GWR building under the point, best effort) - finds the newest per-sheet swisstopo swissBUILDINGS3D 3.0 Beta tile via the data.geo.admin.ch STAC API (FileGDB, cached under cache/building3d/), clips all buildings intersecting the ±radius_m box around the point (whole buildings, not sliced; layers Building_solid + Roof_solid), builds an OBJ/MTL mesh (local origin at the point, Y-up) + a georeferenced DXF (3DFACE mesh on WALL/ROOF layers, absolute LV95/LN02 metres - opens in AutoCAD/BricsCAD/Rhino/SketchUp) + a DWG R2000 (same mesh, converted from the DXF via LibreDWG when available) + GeoPackage (EPSG:2056, full attributes incl. EGID and heights) + preview render + README, and zips it as `<slug>_3d_<radius>m.zip`.

Responds 202 immediately with a `jobId`. Two ways to get the result: (1) give an `email` and the zip (or a failure report - unknown address, no tile covering the point, empty box) arrives by Resend in about a minute, exactly as before; (2) poll `statusUrl` until `status` is `ready`, then GET `downloadUrl` (the zip) or `downloadUrl/{file}` for one member. Without `email` no mail is sent at all. Outputs are kept under cache/building3d/jobs/<jobId>/ and evicted least-recently-used once the jobs directory exceeds its cap (default 5 GiB, `BUILDING3D_JOBS_MAX_BYTES`); the download endpoints touch a job on every fetch, so a result that keeps being fetched stays.

Concurrency: at most 3 extractions per pm2 worker (RES runs 2 cluster workers, so 6 box-wide); beyond that the request is answered 429 and should simply be retried a minute later.

## Authentication

- `ResApiToken`, apiKey, in header, header `token`

## Request body

_Required._

Any of:

- `Option 1`
- `Option 2`

## Example requests

### Hub form (address + e-mail)

```json
{
  "address": "Rainweg 1 4710 Balsthal",
  "radius_m": 10,
  "email": "someone@example.com"
}
```

### Grasshopper (point, no e-mail, poll + download)

```json
{
  "lat": 47.3159,
  "lng": 7.6931,
  "radius_m": 25
}
```

## Code samples

### cURL

```bash
curl --request POST \
  --url https://res.aireon.ch/res_api/building3d-extract \
  --header 'Content-Type: application/json' \
  --header 'token: <token>' \
  --data '
{
  "address": "Rainweg 1 4710 Balsthal",
  "radius_m": 10,
  "email": "someone@example.com"
}
'
```

### TypeScript

```typescript
const url = 'https://res.aireon.ch/res_api/building3d-extract';
const options = {
  method: 'POST',
  headers: {'Content-Type': 'application/json', token: '<token>'},
  body: JSON.stringify({address: 'Rainweg 1 4710 Balsthal', radius_m: 10, email: 'someone@example.com'})
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));
```

### Python

```python
import requests

url = "https://res.aireon.ch/res_api/building3d-extract"

payload = {
    "address": "Rainweg 1 4710 Balsthal",
    "radius_m": 10,
    "email": "someone@example.com"
}
headers = {
    "Content-Type": "application/json",
    "token": "<token>"
}

response = requests.post(url, json=payload, headers=headers)

print(response.text)
```

## Responses

### 202

Job accepted and the worker spawned. Legacy fields (`ok`, `address`, `radius_m`, `email`, `note`) are unchanged for the hub form; the job fields tell an API client where to poll and download.

#### Example

```json
{
  "ok": true,
  "address": "string",
  "lat": 0,
  "lng": 0,
  "radius_m": 0,
  "email": "string",
  "note": "string",
  "jobId": "9d222c6d-893e-4e79-8201-3c9ca16a0f39",
  "status": "queued",
  "statusUrl": "string",
  "downloadUrl": "string"
}
```

- `ok` (`startBuilding3dExtract.response.202.ok`; boolean, required): Always `true` on 202 (legacy hub field).
- `address` (`startBuilding3dExtract.response.202.address`; string | null, required): The address as received, or `null` for a point request.
- `radius_m` (`startBuilding3dExtract.response.202.radius_m`; number, required): The clamped radius actually used.
- `email` (`startBuilding3dExtract.response.202.email`; string | null, required): Recipient of the result e-mail, or `null` when none will be sent.
- `note` (`startBuilding3dExtract.response.202.note`; string, required): Human-readable hint on how the result will arrive.
- `jobId` (`startBuilding3dExtract.response.202.jobId`; string, required)
  - format `uuid`
- `status` (`startBuilding3dExtract.response.202.status`; string, required)
  - one of `"queued"`
- `statusUrl` (`startBuilding3dExtract.response.202.statusUrl`; string, required): `/res_api/building3d-extract/status/{jobId}` - poll until `ready`.
- `downloadUrl` (`startBuilding3dExtract.response.202.downloadUrl`; string, required): `/res_api/building3d-extract/download/{jobId}` - the zip once `ready`; append `/{file}` for one member.
- `lat` (`startBuilding3dExtract.response.202.lat`; number | null, optional)
- `lng` (`startBuilding3dExtract.response.202.lng`; number | null, optional)

### 400

Neither a usable `address` (4–200 chars) nor a `lat`+`lng` pair inside Switzerland, or an invalid `email`.

#### Example

```json
{
  "error": "string",
  "message": "string"
}
```

- `error` (`startBuilding3dExtract.response.400.error`; string, required): Human-readable error description.
- `message` (`startBuilding3dExtract.response.400.message`; string, optional): Optional underlying error message.

### 401

Authentication missing or invalid.

#### Example

```json
{
  "error": "string",
  "message": "string"
}
```

- `error` (`startBuilding3dExtract.response.401.error`; string, required): Human-readable error description.
- `message` (`startBuilding3dExtract.response.401.message`; string, optional): Optional underlying error message.

### 403

Authenticated but not permitted.

#### Example

```json
{
  "error": "string",
  "message": "string"
}
```

- `error` (`startBuilding3dExtract.response.403.error`; string, required): Human-readable error description.
- `message` (`startBuilding3dExtract.response.403.message`; string, optional): Optional underlying error message.

### 429

All extraction slots busy (3 per pm2 worker, 6 box-wide). Retry in a minute.

#### Example

```json
{
  "error": "string",
  "message": "string"
}
```

- `error` (`startBuilding3dExtract.response.429.error`; string, required): Human-readable error description.
- `message` (`startBuilding3dExtract.response.429.message`; string, optional): Optional underlying error message.

### 500

The job directory could not be created or the worker could not be started.

#### Example

```json
{
  "error": "string",
  "message": "string"
}
```

- `error` (`startBuilding3dExtract.response.500.error`; string, required): Human-readable error description.
- `message` (`startBuilding3dExtract.response.500.message`; string, optional): Optional underlying error message.


Source: https://docs.aireon.ch/res/start-building3d-extract/index.md
