# Overview

## Felt’s Developer Tools

There are a variety of ways to interact with Felt’s modern GIS platform outside of the user interface. They can be grouped into two buckets: tools for programmatically creating and modifying maps, and tools for building custom experiences for map viewers. These tools can be used to solve distinct challenges and also be used in tandem with one another.

### Creating and modifying maps

Felt’s [**REST API**](/rest-api/getting-started) allows editors to interact with the Felt platform via code, performing actions such as creating new maps, adding data to maps, styling layers, and more. The REST API can be leveraged from any environment that is capable of sending GET and POST requests.

For Python users, interactions with the REST API are simplified through the [**`felt-python`**](https://github.com/felt/felt-python) **module**, which can be installed with `pip` and used to call the REST API endpoints directly from Python functions.

### Creating custom applications

Felt’s user interface allows a large amount of customization, offering the ability to generate complex cartographic designs, adding components to create a dashboard, and much more.

However, sometimes application developers need further control over the experience of viewing and/or interacting with a map. For example, they may want to run custom logic after a user clicks on a feature in a layer, or animate data on the map based on other types of user input elsewhere on the webpage. For these situations and many more, Felt’s [JavaScript SDK](/js-sdk/getting-started) allows developers to programmatically control maps in two ways:\
\
[**Extensions**](https://help.felt.com/dashboards-and-apps/extensions) let you write custom code directly within Felt maps, with access to all SDK functionality. Alternatively, you can [**embed**](/js-sdk/controlling-maps) Felt maps into your own applications and use the SDK to control the embedded map experience.

{% hint style="info" %}

#### Note: looking for Element-related documentation? They are now called Annotations.

References have been updated in the app and documentation, while naming in the REST API and JS SDK remains unchanged. See [Working with annotations](/rest-api/working-with-annotations) and [Drawing annotations](/js-sdk/drawing-annotations) for more.
{% endhint %}


# Getting started

The Felt REST API allows you to programmatically interact with the Felt platform, enabling you to integrate Felt's powerful mapping capabilities into your own workflows and pipelines.

You are able to create and manipulate Maps, Layers, Annotations, Sources, Projects and more.

The REST API is available on select [Enterprise plans](https://felt.com/pricing). Reach out to [our team](https://felt.com/sales) to learn more or set up a trial.

## Endpoints

All Felt API endpoints are hosted at the following base URL:

```
https://felt.com/api/v2
```

## Create an API token

All calls to the Felt API must be authenticated. The easiest way to authenticate your API calls is by creating an API token and providing it as a `Bearer` token in the request header.

You can create an API token in the [Developers tab of the Workspace Settings page](https://felt.com/maps/latest/developers):

<figure><img src="/files/nJbokfNwhiwi82oG4EoR" alt=""><figcaption><p>You can generate as many API tokens as you need. Make sure to copy them to a secure location!</p></figcaption></figure>

Learn more about API tokens here:

{% content-ref url="/pages/1jwIRLefovDWFByxurK3" %}
[Authentication](/rest-api/authentication)
{% endcontent-ref %}

## Install our Python library (optional)

The easiest way to interact with the Felt API is by using our `felt-python` SDK. You can install it with the following command:

```bash
pip install felt-python
```

## Example: Creating a new map

For map management beyond creation — reading details, deleting, moving between projects — see [Navigating maps and workspaces](/rest-api/navigating-maps-and-workspaces).

Creating a new map is as simple as making a POST request to the maps endpoint.

{% tabs %}
{% tab title="curl" %}

```bash
# Your API token should look like this:
# FELT_API_TOKEN="felt_pat_ABCDEFUDQPAGGNBmX40YNhkCRvvLI3f8/BCwD/g8"
FELT_API_TOKEN="<YOUR_API_TOKEN>"

curl \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  "https://felt.com/api/v2/maps" \
  -d '{"title": "My newly created map"}'
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

# This looks like:
# api_token = "felt_pat_ABCDEFUDQPAGGNBmX40YNhkCRvvLI3f8/BCwD/g8"
api_token = "<YOUR_API_TOKEN>"

r = requests.post(
  "https://felt.com/api/v2/maps",
  json={"title": "My newly created map"},
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
map_id = r.json()["id"]

print(r.json())
```

{% endtab %}

{% tab title="felt-python" %}

```python
import os

from felt_python import create_map

# Setting your API token as an env variable can save
# you from repeating it in every function call
os.environ["FELT_API_TOKEN"] = "<YOUR_API_TOKEN>"

response = create_map(
    title="My newly created map",
    lat=40,
    lon=-3,
)
map_id = response["id"]
```

{% endtab %}
{% endtabs %}

You should see a response like this (trimmed for brevity):

```json
{
  "id": "CjU1CMJPTAGofjOK3ICf1D",
  "type": "map",
  "title": "My newly created map",
  "url": "https://felt.com/map/My-newly-created-map-CjU1CMJPTAGofjOK3ICf1D",
  "public_access": "view_only",
  "layers": [],
  "created_at": "2024-05-25T15:51:34"
}
```

Notice the `"id"` property. Every map has a unique ID, which is also part of the map's URL. Let's take note of it for future API calls.

Also part of the response is a `"url"` property, which is the URL to your newly-created map. Feel free to open it! For now, it should just show a blank map.

## Example: Uploading a layer from a URL

This example uploads from a URL; for file uploads and monitoring processing, see [Uploading files and URLs](/rest-api/uploading-files-and-urls).

Now that we've created a new map, let's add some data to it. We'll need the `map_id` included in the previous call's response.

Felt supports [many kinds of file and URL imports](https://help.felt.com/upload-anything/files). In this case, we'll import all the recent earthquakes from [the USGS' live GeoJSON feed](https://earthquake.usgs.gov/earthquakes/feed/v1.0/geojson.php):

{% tabs %}
{% tab title="curl" %}

```bash
# Store the map ID from the previous call:
# MAP_ID="CjU1CMJPTAGofjOK3ICf1D"
MAP_ID="<YOUR_MAP_ID>"

curl \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  "https://felt.com/api/v2/maps/${MAP_ID}/upload" \
  -d '{"import_url":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson", "name": "USGS Earthquakes"}'
```

{% endtab %}

{% tab title="Python" %}

```python
r = requests.post(
  f"https://felt.com/api/v2/maps/{map_id}/upload",
  json={
    "import_url":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson",
    "name": "USGS Earthquakes",
  },
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
layer_id = r.json()["layer_id"]

print(r.json())
```

{% endtab %}

{% tab title="felt-python" %}

```python
from felt_python import upload_url

url_upload = upload_url(
    map_id=map_id,
    layer_url="https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson",
    layer_name="USGS Earthquakes",
)
layer_id = url_upload["layer_id"]
```

{% endtab %}
{% endtabs %}

Like maps, layers also have unique identifiers. Let's take note of this one (the `layer_id` field in the response) so we can style it in the next call.

You can see the uploaded result in your map:

<figure><img src="/files/49w9NCai7FjXRgXr76Gz" alt=""><figcaption><p>Since we imported a live data feed, the points on your layer may look different.</p></figcaption></figure>

## Example: Styling a layer

For reading a layer's current style and more styling patterns, see [Styling layers](/rest-api/styling-layers).

{% hint style="info" %}
Layer styles are defined in the [Felt Style Language](/felt-style-language/getting-started), a JSON-based specification that allows customizing a layer's style, legend, label and popups.
{% endhint %}

Layers can be styled at upload time or afterwards. Let's change the style of our newly-created earthquakes layer so that points are bigger and in <mark style="color:green;">green</mark> color:

{% tabs %}
{% tab title="curl" %}

```bash
# Store the layer ID from the previous call:
# LAYER_ID="F1XyzAB2TQi9CDeFgHiJkL"
LAYER_ID="<YOUR_LAYER_ID>"

curl \
  -X POST \
  "https://felt.com/api/v2/maps/${MAP_ID}/layers/${LAYER_ID}/update_style" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  --data '{"style": {"paint": {"color": "green", "opacity": 0.9, "size": 30, "strokeColor": "auto", "strokeWidth": 1}, "legend": {}, "type": "simple", "version": "2.3.1"}}'
```

{% endtab %}

{% tab title="Python" %}

```python
new_fsl = {
  "paint": {
    "color": "green",
    "opacity": 0.9,
    "size": 30,
    "strokeColor": "auto",
    "strokeWidth": 1
  },
  "legend": {},
  "type": "simple",
  "version": "2.3.1"
}

r = requests.post(
  f"https://felt.com/api/v2/maps/{map_id}/layers/{layer_id}/update_style",
  json={"style": new_fsl},
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
print(r.json())
```

{% endtab %}

{% tab title="felt-python" %}

```python
from felt_python import update_layer_style

new_fsl = {
  "paint": {
    "color": "green",
    "opacity": 0.9,
    "size": 30,
    "strokeColor": "auto",
    "strokeWidth": 1
  },
  "legend": {},
  "type": "simple",
  "version": "2.3.1"
}

update_layer_style(
    map_id=map_id,
    layer_id=layer_id,
    style=new_fsl,
)
```

{% endtab %}
{% endtabs %}

Go to your map to see how your new layer looks:

<figure><img src="/files/COYyovHnUGbj7Fle6tVO" alt=""><figcaption><p>Since we imported a live data feed, the points on your layer may look different.</p></figcaption></figure>

## Example: Refreshing a live data layer

For refreshing file-based layers and the full refresh flow, see [Refreshing live data layers](/rest-api/refreshing-live-data-layers).

{% hint style="warning" %}
A layer must have finished uploading successfully before it can be refreshed
{% endhint %}

Similar to [a URL upload](/rest-api/uploading-files-and-urls), refreshing an existing URL layer is just a matter of making a single `POST` request:

{% tabs %}
{% tab title="curl" %}

```bash
curl \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  "https://felt.com/api/v2/maps/${MAP_ID}/layers/${LAYER_ID}/refresh"
```

{% endtab %}

{% tab title="Python" %}

```python
r = requests.post(
  f"https://felt.com/api/v2/maps/{map_id}/layers/{layer_id}/refresh",
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
print(r.json())
```

{% endtab %}

{% tab title="felt-python" %}

```python
from felt_python import refresh_url_layer

refresh_url_layer(map_id, layer_id)
```

{% endtab %}
{% endtabs %}

Now go to your map and see if any new earthquakes have occurred!

## Next steps

* [Authentication](/rest-api/authentication) — token behavior, rotation, and security.
* [Errors and rate limits](/rest-api/errors-and-rate-limits) — what failures look like and how to handle them.
* [Listening to updates using webhooks](/rest-api/listening-to-updates-using-webhooks) — react to map changes without polling.
* [API Reference](/rest-api/api-reference) — every endpoint, generated from the OpenAPI spec.


# Authentication

All calls to the Felt API authenticate with a personal access token sent as a `Bearer` token in the request header:

```http
Authorization: Bearer <API Token>
```

Tokens start with the `felt_pat_` prefix. Since a token grants access to your account, store it securely — treat it like a password. Prefer environment variables or a secrets manager over hardcoding tokens in source code.

## Creating a token

You can create an API token in the [Developers tab of the Workspace Settings page](https://felt.com/maps/latest/developers):

<figure><img src="/files/sfwubRDKAIdShrjmBZku" alt=""><figcaption><p>Generate as many API tokens as you need</p></figcaption></figure>

<div><figure><img src="/files/yljedkWCSC2stEUNH5bW" alt=""><figcaption><p>Give your API token a unique name</p></figcaption></figure> <figure><img src="/files/FiJ7IB6GfEi6fvw7ri7E" alt=""><figcaption><p>Make sure to copy your token to a secure location</p></figcaption></figure></div>

Be sure to take note of the token before closing the dialog; you won't have a second chance to view it. Only a hash of your token is stored on Felt's servers.

## How tokens behave

* **Workspace-scoped.** Each token belongs to the workspace it was created in and can only access that workspace's resources. Requests against another workspace's resources fail with `401`.
* **They act as you.** Requests made with your token have your account's permissions in that workspace.
* **No expiry.** Tokens remain valid until revoked. To rotate a token, create a new one in workspace settings, switch your integration over, and revoke the old one.
* **Failed authentication returns `401`** with a JSON body explaining the problem — see [Errors and rate limits](/rest-api/errors-and-rate-limits).

## Making an authenticated request

{% tabs %}
{% tab title="curl" %}

```bash
# This looks like:
# FELT_API_TOKEN="felt_pat_ABCDEFUDQPAGGNBmX40YNhkCRvvLI3f8/BCwD/g8"
FELT_API_TOKEN="<YOUR_API_TOKEN>"

curl \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  "https://felt.com/api/v2/user"
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

# This looks like:
# api_token = "felt_pat_ABCDEFUDQPAGGNBmX40YNhkCRvvLI3f8/BCwD/g8"
api_token = "<YOUR_API_TOKEN>"

r = requests.get(
  "https://felt.com/api/v2/user",
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
print(r.json())
```

{% endtab %}
{% endtabs %}

A successful response returns your user details, confirming the token works:

```json
{
  "id": "AbC123dEfG456hIjK789lM",
  "type": "user",
  "name": "Your Name",
  "email": "you@example.com"
}
```

From here, head to [Getting started](/rest-api/getting-started) to create your first map via the API.


# Navigating maps and workspaces

## Workspaces and API tokens

Workspaces are the place where users in the same organization collaborate and share maps. A user may form part of several workspaces but, at the very least, always forms part of one.

API tokens are created per-workspace. If you wish to interact with several workspaces via the Felt API, you must create a different API token for each one.

## Working with maps

### Creating a new map

Creating a new map is as simple as making a `POST` request to the maps endpoint.

{% tabs %}
{% tab title="curl" %}

```bash
# Your API token should look like this:
# FELT_API_TOKEN="felt_pat_ABCDEFUDQPAGGNBmX40YNhkCRvvLI3f8/BCwD/g8"
FELT_API_TOKEN="<YOUR_API_TOKEN>"

curl \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  "https://felt.com/api/v2/maps" \
  -d '{"title": "My newly created map"}'
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

# Your API token should look like this:
# api_token = "felt_pat_ABCDEFUDQPAGGNBmX40YNhkCRvvLI3f8/BCwD/g8"
api_token = "<YOUR_API_TOKEN>"

r = requests.post(
  "https://felt.com/api/v2/maps",
  json={"title": "My newly created map"},
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
map_id = r.json()["id"]

print(r.json())
```

{% endtab %}

{% tab title="felt-python" %}

```python
import os

from felt_python import create_map

# Setting your API token as an env variable can save
# you from repeating it in every function call
os.environ["FELT_API_TOKEN"] = "<YOUR_API_TOKEN>"

response = create_map(
    title="My newly created map",
    lat=40,
    lon=-3,
    public_access="private",
)
map_id = response["id"]
```

{% endtab %}
{% endtabs %}

You should see a response like this (trimmed for brevity):

```json
{
  "id": "CjU1CMJPTAGofjOK3ICf1D",
  "type": "map",
  "title": "My newly created map",
  "url": "https://felt.com/map/My-newly-created-map-CjU1CMJPTAGofjOK3ICf1D",
  "public_access": "view_only",
  "layers": [],
  "created_at": "2024-05-25T15:51:34"
}
```

Notice the `"id"` property. Every map has a unique ID, which is also part of the map's URL. Let's take note of it for future API calls.

Also part of the response is a `"url"` property, which is the URL to your newly-created map.

### Getting a map's details

Performing a `GET` request to a map URL will give you useful information about that map, including title, URL, layers, thumbnail URL, creation and visited timestamps.

{% tabs %}
{% tab title="curl" %}

```bash
curl \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  "https://felt.com/api/v2/maps/${MAP_ID}"
```

{% endtab %}

{% tab title="Python" %}

```python
r = requests.get(
  f"https://felt.com/api/v2/maps/{map_id}",
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
print(r.json())
```

{% endtab %}

{% tab title="felt-python" %}

```python
from felt_python import get_map

get_map(map_id)
```

{% endtab %}
{% endtabs %}

### Deleting a map

To remove a map from your workspace, simply perform a `DELETE` request to the map's URL:

{% tabs %}
{% tab title="curl" %}

```bash
curl \
  -X DELETE \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  "https://felt.com/api/v2/maps/${MAP_ID}"
```

{% endtab %}

{% tab title="Python" %}

```python
r = requests.delete(
  f"https://felt.com/api/v2/maps/{map_id}",
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
```

{% endtab %}

{% tab title="felt-python" %}

```python
from felt_python import delete_map

delete_map(map_id)
```

{% endtab %}
{% endtabs %}

### Moving a map

To move a map to a different folder or project, send a POST request to the map's move URL with either a `project_id` or a `folder_id` in the body. You can find project IDs by listing your projects with `GET /api/v2/projects`:

{% tabs %}
{% tab title="curl" %}

```bash
curl \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  "https://felt.com/api/v2/maps/${MAP_ID}/move" \
  -d "{\"project_id\": \"${PROJECT_ID}\"}"
```

{% endtab %}

{% tab title="Python" %}

```python
r = requests.post(
  f"https://felt.com/api/v2/maps/{map_id}/move",
  json={"project_id": project_id},
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
print(r.json())
```

{% endtab %}

{% tab title="felt-python" %}

```python
from felt_python import move_map

move_map(map_id, project_id)
```

{% endtab %}
{% endtabs %}


# Uploading files and URLs

{% hint style="info" %}
Felt supports a myriad of formats, both as files and hosted URLs, up to a limit of 5GB. Check out the full list [in our Help Center](https://help.felt.com/upload-anything/files).
{% endhint %}

## Uploading a URL

The easiest way of uploading data into a Felt map via the API is to import from a URL. Here's an example importing all the recent earthquakes from [the USGS' live GeoJSON feed](https://earthquake.usgs.gov/earthquakes/feed/v1.0/geojson.php):

{% tabs %}
{% tab title="curl" %}

```bash
# Your API token and map ID should look like this:
# FELT_API_TOKEN="felt_pat_ABCDEFUDQPAGGNBmX40YNhkCRvvLI3f8/BCwD/g8"
# MAP_ID="CjU1CMJPTAGofjOK3ICf1D"
FELT_API_TOKEN="<YOUR_API_TOKEN>"
MAP_ID="<YOUR_MAP_ID>"

curl \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  "https://felt.com/api/v2/maps/${MAP_ID}/upload" \
  -d '{"import_url":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson", "name": "USGS Earthquakes"}'
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

# Your API token should look like this:
# api_token = "felt_pat_ABCDEFUDQPAGGNBmX40YNhkCRvvLI3f8/BCwD/g8"
api_token = "<YOUR_API_TOKEN>"
map_id = "<YOUR_MAP_ID>"

r = requests.post(
  f"https://felt.com/api/v2/maps/{map_id}/upload",
  json={
    "import_url":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson",
    "name": "USGS Earthquakes",
  },
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
layer_id = r.json()["layer_id"]
```

{% endtab %}

{% tab title="felt-python" %}

```python
import os

from felt_python import upload_url

# Setting your API token as an env variable can save
# you from repeating it in every function call
os.environ["FELT_API_TOKEN"] = "<YOUR_API_TOKEN>"

map_id = "<YOUR_MAP_ID>"

url_upload = upload_url(
    map_id=map_id,
    layer_url="https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson",
    layer_name="USGS Earthquakes",
)
layer_id = url_upload["layer_id"]
```

{% endtab %}
{% endtabs %}

Like maps, layers also have unique identifiers. Make sure to take note of them for subsequent calls, like styling a layer or removing it.

## Uploading a file

{% hint style="info" %}
Uploading a file is a single function call using the `felt-python` library.
{% endhint %}

Files aren't uploaded to the Felt app — instead, they're uploaded directly to Amazon S3. Therefore, creating a layer from a file on your computer is a two-step process:

### 1. Request an upload via the Felt API

Perform a `POST` request to receive an S3 presigned URL which you can later upload your files to:

{% tabs %}
{% tab title="Python" %}

```python
r = requests.post(
    f"https://felt.com/api/v2/maps/{map_id}/upload",
    headers={
        "Authorization": f"Bearer {api_token}",
        "Content-Type": "application/json",
    },
    json={"name": "My new layer"},
)
assert r.ok
layer_id = r.json()["layer_id"]

presigned_upload = r.json()
```

{% endtab %}

{% tab title="felt-python" %}

```python
from felt_python import upload_file

file_name = "<YOUR_FILE_WITH_EXTENSION>" # Example: regions.geojson

upload_file(
  map_id=map_id,
  file_name=file_name,
  layer_name="My new layer",
)
```

{% endtab %}
{% endtabs %}

### 2. Upload your file(s) to Amazon S3

{% tabs %}
{% tab title="Python" %}

```python
# This code is a continuation of the previous Python code block
# and assumes you already have a "presigned_upload" variable

file_name = "<YOUR_FILE_WITH_EXTENSION>" # Example: regions.geojson

url = presigned_upload["url"]
presigned_attributes = presigned_upload["presigned_attributes"]
# A 204 response indicates that the upload was successful
with open(file_name, "rb") as file_obj:
    output = requests.post(
        url,
        # Order is important, file should come at the end
        files={**presigned_attributes, "file": file_obj},
    )
```

{% endtab %}

{% tab title="felt-python" %}

```python
# Nothing! Uploading a file is a single step with the felt-python library
```

{% endtab %}
{% endtabs %}

## Monitoring progress

You can check the upload status of a layer by querying it. The response includes two useful fields:

* `progress` — a number from 0 to 100, the percentage complete. Typed as a float in the API spec, so don't assume an integer.
* `status` — one of `uploading`, `processing`, `completed`, or `failed`.

Poll until `status` is `completed` (or `failed`). A layer must reach `completed` before it can be styled or [refreshed](/rest-api/refreshing-live-data-layers).

{% tabs %}
{% tab title="curl" %}

```bash
curl \
  "https://felt.com/api/v2/maps/${MAP_ID}/layers/${LAYER_ID}" \
  -H "Authorization: Bearer ${FELT_API_TOKEN}"
```

{% endtab %}

{% tab title="Python" %}

```python
r = requests.get(
    f"https://felt.com/api/v2/maps/{map_id}/layers/{layer_id}",
    headers={"Authorization": f"Bearer {api_token}"},
)
assert r.ok
print(r.json()["progress"])
```

{% endtab %}

{% tab title="felt-python" %}

```python
from felt_python import get_layer_details

get_layer_details(map_id, layer_id)["progress"]
```

{% endtab %}
{% endtabs %}


# Styling layers

## Understanding layer styles

A layer's style is defined in a JSON-based format called [the Felt Style Language](/felt-style-language/getting-started), or FSL for short. Editors can view the current style of a layer inside a Felt map by clicking on `Actions > Edit style language` in a layer's overflow menu (three dots).

Here is an example of a simple visualization, expressed in FSL:

```json
{
  "config": {"labelAttribute": ["type"]},
  "legend": {},
  "paint": {
    "color": "blue",
    "opacity": 0.9,
    "size": 30,
    "strokeColor": "auto",
    "strokeWidth": 1
  },
  "type": "simple",
  "version": "2.3.1"
}
```

## Fetching a layer's current style

A layer's FSL can be retrieved by performing a simple `GET` request to a layer's endpoint — the response's `style` field contains the current style object:

{% tabs %}
{% tab title="curl" %}

```bash
# Your API token should look like this:
# FELT_API_TOKEN="felt_pat_ABCDEFUDQPAGGNBmX40YNhkCRvvLI3f8/BCwD/g8"
FELT_API_TOKEN="<YOUR_API_TOKEN>"
MAP_ID="<YOUR_MAP_ID>"
LAYER_ID="<YOUR_LAYER_ID>"

curl \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  "https://felt.com/api/v2/maps/${MAP_ID}/layers/${LAYER_ID}"
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

# Your API token should look like this:
# api_token = "felt_pat_ABCDEFUDQPAGGNBmX40YNhkCRvvLI3f8/BCwD/g8"
api_token = "<YOUR_API_TOKEN>"
map_id = "<YOUR_MAP_ID>"
layer_id = "<YOUR_LAYER_ID>"

r = requests.get(
  f"https://felt.com/api/v2/maps/{map_id}/layers/{layer_id}",
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
print(r.json())
```

{% endtab %}

{% tab title="felt-python" %}

```python
import os

from felt_python import get_layer_details

# Setting your API token as an env variable can save
# you from repeating it in every function call
os.environ["FELT_API_TOKEN"] = "<YOUR_API_TOKEN>"

map_id = "<YOUR_MAP_ID>"
layer_id = "<YOUR_LAYER_ID>"

layer_details = get_layer_details(map_id, layer_id)
current_style = layer_details["style"]
```

{% endtab %}
{% endtabs %}

## Updating an existing layer's style

To update a layer's style, we can send a `POST` request with the new FSL to the same layer's `/update_style` endpoint.

{% tabs %}
{% tab title="curl" %}

```bash
curl \
  -X POST \
  "https://felt.com/api/v2/maps/${MAP_ID}/layers/${LAYER_ID}/update_style" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  --data '{"style": {"paint": {"color": "green", "opacity": 0.9, "size": 30, "strokeColor": "auto", "strokeWidth": 1}, "legend": {}, "type": "simple", "version": "2.3.1"}}'
```

{% endtab %}

{% tab title="Python" %}

```python
new_fsl = {
  "paint": {
    "color": "green",
    "opacity": 0.9,
    "size": 30,
    "strokeColor": "auto",
    "strokeWidth": 1
  },
  "legend": {},
  "type": "simple",
  "version": "2.3.1"
}

r = requests.post(
  f"https://felt.com/api/v2/maps/{map_id}/layers/{layer_id}/update_style",
  json={"style": new_fsl},
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
print(r.json())
```

{% endtab %}

{% tab title="felt-python" %}

```python
from felt_python import update_layer_style

new_fsl = {
  "paint": {
    "color": "green",
    "opacity": 0.9,
    "size": 30,
    "strokeColor": "auto",
    "strokeWidth": 1
  },
  "legend": {},
  "type": "simple",
  "version": "2.3.1"
}

update_layer_style(
    map_id=map_id,
    layer_id=layer_id,
    style=new_fsl,
)
```

{% endtab %}
{% endtabs %}

## FSL examples

You can find examples of FSL for different visualization types in [the Felt Style Language section](/felt-style-language/types-of-visualizations) of these docs:

* [Simple visualizations](/felt-style-language/types-of-visualizations/simple-visualizations): same color and size for all features (vector) or pixels (raster).
* [Categorical visualizations](/felt-style-language/types-of-visualizations/categorical-visualizations): different color per feature or pixel, based on a categorical attribute
* [Numeric visualizations](/felt-style-language/types-of-visualizations/numeric-visualizations-color-and-size): different color or size per feature or pixel, based on a numeric attribute.
* [Heatmaps](/felt-style-language/types-of-visualizations/heatmaps): a density-based visualization style, for vector point layers.
* [H3](/felt-style-language/types-of-visualizations/h3): aggregate points into hexagonal bins.
* [Raster visualizations](/felt-style-language/types-of-visualizations/raster): imagery, single/multiband numeric, categorical, and hillshade styling for raster layers.


# Refreshing live data layers

It's common to have data update on a regular basis, such as every week or every month. Instead of having to re-upload and style the new data, it can be very convenient to simply refresh a layer using a new data source.

{% hint style="warning" %}
A layer must have finished uploading successfully (`status` of `completed`) before it can be refreshed — see [Monitoring progress](/rest-api/uploading-files-and-urls#monitoring-progress)
{% endhint %}

## Refreshing a layer with a file

{% hint style="info" %}
Refreshing a file layer is a single function call using the `felt-python` library.
{% endhint %}

Just like [regular file uploads](/rest-api/uploading-files-and-urls), refreshing a layer with a new file is a two-step process:

### 1. Request a refresh via the Felt API

Perform a `POST` request to receive an S3 presigned URL which you can later upload your files to:

{% tabs %}
{% tab title="Python" %}

```python
import requests

# Your API token should look like this:
# api_token = "felt_pat_ABCDEFUDQPAGGNBmX40YNhkCRvvLI3f8/BCwD/g8"
api_token = "<YOUR_API_TOKEN>"
map_id = "<YOUR_MAP_ID>"
layer_id = "<YOUR_LAYER_ID>"

r = requests.post(
  f"https://felt.com/api/v2/maps/{map_id}/layers/{layer_id}/refresh",
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
presigned_upload = r.json()
```

{% endtab %}

{% tab title="felt-python" %}

```python
import os

from felt_python import refresh_file_layer

# Setting your API token as an env variable can save
# you from repeating it in every function call
os.environ["FELT_API_TOKEN"] = "<YOUR_API_TOKEN>"

map_id = "<YOUR_MAP_ID>"
layer_id = "<YOUR_LAYER_ID>"
new_file_name = "<PATH_TO_NEW_FILE>"

refresh_file_layer(
    map_id=map_id,
    layer_id=layer_id,
    file_name=new_file_name
)
```

{% endtab %}
{% endtabs %}

### 2. Upload your file(s) to Amazon S3

{% tabs %}
{% tab title="Python" %}

```python
# This code is a continuation of the previous Python code block
# and assumes you already have a "presigned_upload" variable

file_name = "<YOUR_FILE_WITH_EXTENSION>"  # Example: regions.geojson

url = presigned_upload["url"]
presigned_attributes = presigned_upload["presigned_attributes"]
# A 204 response indicates that the upload was successful
with open(file_name, "rb") as file_obj:
    output = requests.post(
        url,
        # Order is important, file should come at the end
        files={**presigned_attributes, "file": file_obj},
    )
```

{% endtab %}

{% tab title="felt-python" %}

```python
# Nothing! Refreshing a file layer is a single step with the felt-python library
```

{% endtab %}
{% endtabs %}

## Refreshing a layer with a URL

Similar to [a URL upload](/rest-api/uploading-files-and-urls), refreshing an existing URL layer is just a matter of making a single `POST` request. The refresh re-fetches the layer's existing import URL — the request takes no body, so the URL itself cannot be changed during a refresh:

{% tabs %}
{% tab title="curl" %}

```bash
# Your API token and map ID should look like this:
# FELT_API_TOKEN="felt_pat_ABCDEFUDQPAGGNBmX40YNhkCRvvLI3f8/BCwD/g8"
FELT_API_TOKEN="<YOUR_API_TOKEN>"
MAP_ID="<YOUR_MAP_ID>"
LAYER_ID="<YOUR_LAYER_ID>"

curl \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  "https://felt.com/api/v2/maps/${MAP_ID}/layers/${LAYER_ID}/refresh"
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

# Your API token and map ID should look like this:
# api_token = "felt_pat_ABCDEFUDQPAGGNBmX40YNhkCRvvLI3f8/BCwD/g8"
api_token = "<YOUR_API_TOKEN>"
map_id = "<YOUR_MAP_ID>"
layer_id = "<YOUR_LAYER_ID>"

r = requests.post(
  f"https://felt.com/api/v2/maps/{map_id}/layers/{layer_id}/refresh",
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
print(r.json())
```

{% endtab %}

{% tab title="felt-python" %}

```python
from felt_python import refresh_url_layer

refresh_url_layer(map_id, layer_id)
```

{% endtab %}
{% endtabs %}


# Working with annotations

{% hint style="info" %}

#### Note: Annotations were previously referred to as Elements.

References have been updated in the app and documentation, while REST API endpoint and JS SDK method naming remain unchanged.
{% endhint %}

Annotations sit on top of all data layers on a map. They are usually drawn in the Felt app, but can also be created and updated via the API.

{% hint style="info" %}
Combining annotations with [webhooks](/rest-api/listening-to-updates-using-webhooks) is a great way to create interactive data apps in Felt.
{% endhint %}

## Listing all annotations on a map

Annotations are returned as a [GeoJSON Feature Collection](https://geojson.org/).

{% tabs %}
{% tab title="curl" %}

```bash
# Your API token and map ID should look like this:
# FELT_API_TOKEN="felt_pat_ABCDEFUDQPAGGNBmX40YNhkCRvvLI3f8/BCwD/g8"
# MAP_ID="CjU1CMJPTAGofjOK3ICf1D"
FELT_API_TOKEN="<YOUR_API_TOKEN>"
MAP_ID="<YOUR_MAP_ID>"

curl \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  "https://felt.com/api/v2/maps/${MAP_ID}/elements"
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

# Your API token and map ID should look like this:
# api_token = "felt_pat_ABCDEFUDQPAGGNBmX40YNhkCRvvLI3f8/BCwD/g8"
api_token = "<YOUR_API_TOKEN>"
map_id = "<YOUR_MAP_ID>"

r = requests.get(
  f"https://felt.com/api/v2/maps/{map_id}/elements",
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
print(r.json())
```

{% endtab %}

{% tab title="felt-python" %}

```python
import os

from felt_python import list_elements

# Setting your API token as an env variable can save
# you from repeating it in every function call
os.environ["FELT_API_TOKEN"] = "<YOUR_API_TOKEN>"

map_id = "<YOUR_MAP_ID>"

list_elements(map_id)
```

{% endtab %}
{% endtabs %}

## Listing all annotation groups

Returns a list of GeoJSON Feature Collections, one for each annotation group.

{% tabs %}
{% tab title="curl" %}

```bash
curl \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  "https://felt.com/api/v2/maps/${MAP_ID}/element_groups"
```

{% endtab %}

{% tab title="Python" %}

```python
r = requests.get(
  f"https://felt.com/api/v2/maps/{map_id}/element_groups",
  headers={"Authorization": f"Bearer {api_token}"}
)
assert r.ok
print(r.json())
```

{% endtab %}

{% tab title="felt-python" %}

```python
from felt_python import list_element_groups

list_element_groups(map_id)
```

{% endtab %}
{% endtabs %}

## Create or update annotations

Each annotation is represented by a feature in the `POST`ed GeoJSON Feature Collection.

For each feature, including an existing annotation ID (`felt:id`) will result in the annotation being updated on the map. If the ID is omitted or does not match an existing annotation, a new annotation is created.

Styling is controlled with `felt:`-prefixed properties on each feature — for example `felt:color`, `felt:opacity`, `felt:strokeWidth`, `felt:strokeStyle`, `felt:size`, and `felt:text` for text annotations. Properties without the `felt:` prefix are stored as the annotation's data attributes. Request bodies are limited to 1 MB, and very complex geometries may be simplified on import.

{% tabs %}
{% tab title="curl" %}

```bash
curl \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  "https://felt.com/api/v2/maps/${MAP_ID}/elements" \
  -d '{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"coordinates":[[[15.478752514432728,15.576176978045694],[15.478752514432728,4.005934587045303],[29.892174099255755,4.005934587045303],[29.892174099255755,15.576176978045694],[15.478752514432728,15.576176978045694]]],"type":"Polygon"}}]}'
```

{% endtab %}

{% tab title="Python" %}

```python
new_elements = {
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      # Include "felt:id" in properties to update an existing annotation.
      # "felt:" properties control styling; anything else is stored as data.
      "properties": {"felt:color": "#2674BA"},
      "geometry": {
        "coordinates": [
          [
            [
              15.478752514432728,
              15.576176978045694
            ],
            [
              15.478752514432728,
              4.005934587045303
            ],
            [
              29.892174099255755,
              4.005934587045303
            ],
            [
              29.892174099255755,
              15.576176978045694
            ],
            [
              15.478752514432728,
              15.576176978045694
            ]
          ]
        ],
        "type": "Polygon"
      }
    }
  ]
}

r = requests.post(
  f"https://felt.com/api/v2/maps/{map_id}/elements",
  headers={"Authorization": f"Bearer {api_token}"},
  json=new_elements
)
assert r.ok
print(r.json())
```

{% endtab %}

{% tab title="felt-python" %}

```python
from felt_python import upsert_elements

new_elements = {
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {},
      "geometry": {
        "coordinates": [
          [
            [
              15.478752514432728,
              15.576176978045694
            ],
            [
              15.478752514432728,
              4.005934587045303
            ],
            [
              29.892174099255755,
              4.005934587045303
            ],
            [
              29.892174099255755,
              15.576176978045694
            ],
            [
              15.478752514432728,
              15.576176978045694
            ]
          ]
        ],
        "type": "Polygon"
      }
    }
  ]
}

upsert_elements(map_id, new_elements)
```

{% endtab %}
{% endtabs %}

## Delete an annotation

Delete an annotation by its ID. Annotation IDs are returned as the `felt:id` property when [listing annotations](#listing-all-annotations-on-a-map).

{% tabs %}
{% tab title="curl" %}

```bash
curl \
  -X DELETE \
  -H "Authorization: Bearer ${FELT_API_TOKEN}" \
  "https://felt.com/api/v2/maps/${MAP_ID}/elements/${ELEMENT_ID}"
```

{% endtab %}

{% tab title="Python" %}

```python
element_id = "<YOUR_ELEMENT_ID>"

r = requests.delete(
  f"https://felt.com/api/v2/maps/{map_id}/elements/{element_id}",
  headers={"Authorization": f"Bearer {api_token}"},
)
assert r.ok
```

{% endtab %}

{% tab title="felt-python" %}

```python
from felt_python import delete_element

element_id = "<YOUR_ELEMENT_ID>"

delete_element(map_id, element_id)
```

{% endtab %}
{% endtabs %}


# Listening to updates using webhooks

A great way of building data-driven apps using Felt is by triggering a workflow whenever something changes on a map, like someone drawing a polygon around an area of interest or updating the details on a pin.

Instead of polling by listing annotations, comments or data layers on a fixed interval, set up a *webhook* and Felt will send your endpoint a notification any time a map is updated. This allows you to build integrations on top, such as sending a Slack message or recalculating statistics for a newly-drawn area.

## How webhooks work

Each webhook is attached to a single map. Whenever the map changes — annotations drawn or edited, layers updated, comments added or resolved — Felt sends a `POST` request with a JSON body to your webhook URL:

```json
{
  "attributes": {
    "type": "map:update",
    "map_id": "Jzjr8gMKSrCOxZ1OSMT49CB",
    "updated_at": "2024-04-29T12:16:46"
  }
}
```

A few things to know about the delivery model:

* **Event types.** `map:update` is currently the only event type. The payload tells you *which* map changed and *when*, but not what changed — use the [annotations](/rest-api/working-with-annotations), layers, or comments endpoints to fetch the current state when you receive an event.
* **Deliveries are coalesced.** A rapid burst of edits to the same map may result in a single notification rather than one per edit. Treat each delivery as "this map changed, re-read it", not as a per-edit event stream.
* **Retries.** If your endpoint does not respond with a 2xx status, delivery is retried up to 10 times with exponential backoff. This means delivery is *at least once*: your handler should be idempotent. Redirects are not followed.
* **Creation is UI-only.** Webhooks are created in workspace settings (see below); there are no REST endpoints for managing them.

## Verifying webhook signatures

Every webhook has a signing key, shown in the workspace settings UI when you create it. Felt signs each delivery with a `felt-signature` HTTP header containing the Base64-encoded HMAC-SHA256 of the raw JSON body, computed with that signing key.

Verify the signature before acting on a payload — it proves the request came from Felt and not from a third party who discovered your URL:

{% tabs %}
{% tab title="Python" %}

```python
import base64
import hashlib
import hmac

def verify_felt_signature(signing_key: str, raw_body: bytes, signature_header: str) -> bool:
    expected = base64.b64encode(
        hmac.new(signing_key.encode(), raw_body, hashlib.sha256).digest()
    ).decode()
    return hmac.compare_digest(expected, signature_header)
```

{% endtab %}

{% tab title="JavaScript (Node)" %}

```javascript
import { createHmac, timingSafeEqual } from "node:crypto";

function verifyFeltSignature(signingKey, rawBody, signatureHeader) {
  const expected = createHmac("sha256", signingKey).update(rawBody).digest("base64");
  return timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}
```

{% endtab %}
{% endtabs %}

Compute the HMAC over the raw request body exactly as received — re-serializing the parsed JSON can change key order or whitespace and produce a different signature.

## Requirements

Two things are needed in order to make use of webhooks:

1. A Felt map which will serve as the basis for the webhook. Updates will be sent whenever something on this map changes.
2. A webhook URL where the updates will be sent in the form of `POST` requests.

## Generating a new webhook

Workspace admins and editors can set up webhooks in the [Developers tab of the Workspace Settings page](https://felt.com/maps/latest/developers).

Simply click on `Create a new webhook`, select a map to listen to changes and paste in a webhook URL where the updates will be sent to.

<figure><img src="/files/xeVbieRM7ciSf6dh7goe" alt=""><figcaption></figcaption></figure>

## Using your new webhook

In order to use webhooks effectively, a receiving service must be set up to trigger actions based on the updates sent by the Felt API. Here are some examples of how to set up a webhook using Felt and an external service.

<details>

<summary>Setting up an example webhook using Pipedream</summary>

[Pipedream's RequestBin ](https://pipedream.com/requestbin)is an easy way to collect webhook requests and even run custom code as a result.

1. Create a free Pipedream account
2. On the left-hand sidebar, navigate to `Sources`, then click on `New source` in the top-right corner.
3. Select `HTTP / Webhook`, then `New Requests (Payload Only)`, and give your newly-created source a name.
4. Copy the endpoint URL. It will look like `https://XXX.m.pipedream.net`
5. In Felt, navigate to the [Developers tab of your workspace settings](https://felt.com/maps/latest/developers) and click on `Create new webhook`
6. Paste the endpoint URL from Pipedream into the `Webhook URL` text field, select your map in the dropdown and click `Create`.

To test your webhook:

1. Navigate to the Felt map that's linked to the webhook
2. Make any change: add a pin, draw with the marker, change the color of a polygon, update sharing permissions...
3. Back in Pipedream, verify that new events appear for the new source. Note that Pipedream wraps the request in its own envelope — the `body` field shown by Pipedream is the payload Felt sent:

```json
{
  "body": {
    "attributes": {
      "type": "map:update",
      "updated_at": "2024-04-29T12:16:46",
      "map_id": "Jzjr8gMKSrCOxZ1OSMT49CB"
    }
  }
}
```

4. You may also configure a script to run using the above input.

</details>

<details>

<summary>Setting up an example webhook using an AWS Lambda</summary>

Serverless functions like AWS Lambda or Google Cloud Functions are an excellent way of triggering code after a map update by setting them to run after a specific HTTP call.

1. In the AWS console, navigate to `Lambda` and click on `Create function`
2. Choose a name and runtime and, under `Advanced Settings`, make sure to check `Enable function URL`
3. Set `Auth type` to `NONE` and click on `Create function`
4. In the next screen, copy the `Function URL`. It should look like `https://{LAMBDA_ID}.lambda-url.{REGION}.on.aws`
5. Continue configuring your Lambda function as usual by editing the code that will run on map updates

In Felt:

1. Navigate to the [Developers tab of your workspace settings](https://felt.com/maps/latest/developers) and click on `Create new webhook`
2. Paste the function URL from AWS into the `Webhook URL` text field, select your map in the dropdown and click `Create`.

Since the function URL uses `Auth type: NONE`, anyone who discovers the URL can invoke it — make sure your function [verifies the `felt-signature` header](#verifying-webhook-signatures) before doing any work.

</details>


# Errors and rate limits

Almost every error returned by the Felt API uses the same JSON envelope: an `errors` array where each entry has a `title`, a human-readable `detail`, usually a stable `code`, and — where applicable — a `source` telling you which header, parameter, or body field caused the problem.

```json
{
  "errors": [
    {
      "title": "Not found",
      "detail": "Map not found",
      "code": "not_found",
      "source": { "parameter": "map_id" }
    }
  ]
}
```

Treat `code` as best-effort rather than guaranteed: a few responses — notably field-validation failures and plan-limit errors — carry only `title` and `detail`. Branch on the HTTP status first, and use `code` to refine when it is present.

## Status codes

| Status | Code                                                                       | Meaning                                                                                                                                                                                                                                                             | What to do                                                                                                                                                                                                                                        |
| ------ | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | `unauthorized`, `invalid_access_token`                                     | Missing, malformed, revoked, or wrong-workspace token. Requests with a valid token for a resource in a *different* workspace also return 401. **Permission failures also return 401**, not 403 — for example, editing or deleting a map your account can only view. | Check the `Authorization: Bearer` header and that the token was created in the same workspace as the resource. If the token is fine, ask a workspace admin for the required role on the resource. See [Authentication](/rest-api/authentication). |
| `403`  | `forbidden`, `over_storage_limit`, `over_processing_limit`, `unauthorized` | Your workspace has hit a plan limit — data hosting or monthly data processing — or the action requires a plan your workspace isn't on.                                                                                                                              | Read `detail`: it names the limit. Reach out to [our team](https://felt.com/sales) to raise it.                                                                                                                                                   |
| `404`  | `not_found`                                                                | The resource doesn't exist. The `source.parameter` field names the offending ID.                                                                                                                                                                                    | Verify the ID. Remember that map IDs come from the map URL, while layer IDs come from API responses.                                                                                                                                              |
| `422`  | `invalid`                                                                  | The request body or parameters failed validation. `source.pointer` identifies the invalid field.                                                                                                                                                                    | Fix the field named in `detail` / `source` and retry.                                                                                                                                                                                             |
| `429`  | `too_many_requests`                                                        | You hit a rate limit (see below).                                                                                                                                                                                                                                   | Back off and retry later.                                                                                                                                                                                                                         |

When handling responses in code, print the error body rather than only asserting success — `detail` almost always tells you exactly what's wrong:

```python
r = requests.post(url, headers=headers, json=body)
if not r.ok:
    print(r.status_code, r.json()["errors"])
    r.raise_for_status()
```

## Rate limits

Two independent limits apply:

1. **Per-IP request throttle** — currently 300 requests per minute per IP address. Exceeding it returns `429` with the `too_many_requests` code above. Spread bulk work out or batch it (for example, upsert many annotations in one `POST /elements` call instead of one call per feature).
2. **Plan usage limits** — depending on your plan, API usage may also be subject to an overall usage limit. Every API response includes an `x-api-limit-exceeded` header (`true` or `false`); if your workspace exceeds its limit, requests return `429`. Reach out to [our team](https://felt.com/sales) if you have questions about your plan's API access.

When you receive a `429`, retry with exponential backoff and jitter rather than immediately — and check `x-api-limit-exceeded` to distinguish the per-IP throttle (`false`) from a plan usage limit (`true`).

## Pagination

List endpoints (`GET /projects`, `GET /library`, `GET /maps/{map_id}/elements`, and so on) currently return **all** results in a single response — there are no pagination parameters. For very large maps, prefer scoping your reads (for example, listing a single element group) over repeatedly fetching full collections.

## Versioning

The current API version is `v2`, served under `https://felt.com/api/v2`. The machine-readable OpenAPI spec for the exact version in production is always available at:

```
GET https://felt.com/api/v2/openapi.json
```


# API Reference


# Maps

APIs for building maps

Maps are the centerpiece of Felt.

With these APIs, you can create, retrieve, update, delete, move, and duplicate maps programmatically.

## Move map

> Move a map to a different project or folder within the same workspace. Project IDs and Folder IDs can be found inside map settings.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Maps are the centerpiece of Felt.\n\nWith these APIs, you can create, retrieve, update, delete, move, and duplicate maps programmatically.\n","name":"Maps"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"MapMoveParams":{"oneOf":[{"properties":{"project_id":{"$ref":"#/components/schemas/FeltID"}},"required":["project_id"],"title":"MoveMapProjectParams","type":"object"},{"properties":{"folder_id":{"$ref":"#/components/schemas/FeltID"}},"required":["folder_id"],"title":"MoveMapFolderParams","type":"object"}],"title":"MapMoveParams","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"Map":{"additionalProperties":false,"properties":{"basemap":{"type":"string"},"created_at":{"format":"date_time","type":"string"},"element_groups":{"items":{"properties":{"elements":{"$ref":"#/components/schemas/GeoJSON"},"id":{"$ref":"#/components/schemas/FeltID"},"name":{"nullable":true,"type":"string"}},"type":"object"},"type":"array"},"elements":{"$ref":"#/components/schemas/GeoJSON"},"folder_id":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layer_groups":{"items":{"$ref":"#/components/schemas/LayerGroup"},"type":"array"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"project_id":{"nullable":false,"type":"string"},"public_access":{"enum":["private","view_only","view_and_comment","view_comment_and_edit"],"type":"string"},"table_settings":{"$ref":"#/components/schemas/MapTableSettings"},"thumbnail_url":{"description":"A static thumbnail image of the map","nullable":true,"type":"string"},"title":{"type":"string"},"type":{"enum":["map"],"type":"string"},"url":{"type":"string"},"viewer_permissions":{"$ref":"#/components/schemas/MapViewerPermissions"},"visited_at":{"format":"date_time","nullable":true,"type":"string"}},"required":["id","type","url","title","thumbnail_url","created_at","visited_at","layers","layer_groups","elements","element_groups","project_id","public_access"],"title":"Map","type":"object"},"GeoJSON":{"properties":{"features":{"items":{"properties":{"geometry":{"properties":{"felt:id":{"$ref":"#/components/schemas/FeltID"},"felt:parentId":{"format":"felt_id","nullable":true,"type":"string"}},"type":"object"},"properties":{"type":"object"},"type":{"enum":["Feature"],"type":"string"}},"type":"object"},"type":"array"},"type":{"enum":["FeatureCollection"],"type":"string"}},"required":["type","features"],"title":"GeoJSON","type":"object"},"LayerGroup":{"additionalProperties":false,"properties":{"caption":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"legend_visibility":{"description":"Controls how the layer group is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"nullable":false,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":false,"type":"integer"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"type":{"enum":["layer_group"],"type":"string"},"visibility_interaction":{"description":"Controls how the layer group is displayed in the legend. Defaults to `\"default\"`.","enum":["default","slider","select","multi_select"],"nullable":false,"type":"string"}},"required":["id","type","name","caption","visibility_interaction","layers"],"title":"LayerGroup","type":"object"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"MapTableSettings":{"additionalProperties":false,"properties":{"default_table_layer_id":{"format":"felt_id","nullable":true,"type":"string"},"viewers_can_open_table":{"description":"Whether viewers can open the data table","type":"boolean"}},"title":"MapTableSettings","type":"object"},"MapViewerPermissions":{"additionalProperties":false,"properties":{"can_duplicate_map":{"description":"Whether viewers can duplicate the map and data","type":"boolean"},"can_export_data":{"description":"Whether viewers can export map data","type":"boolean"},"can_see_map_presence":{"description":"Whether viewers can see who else is viewing the map","type":"boolean"}},"title":"MapViewerPermissions","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/move":{"post":{"callbacks":{},"description":"Move a map to a different project or folder within the same workspace. Project IDs and Folder IDs can be found inside map settings.","operationId":"move_map","parameters":[{"description":"","in":"path","name":"map_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MapMoveParams"}}},"description":"Map move params","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Map"}}},"description":"Map"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Move map","tags":["Maps"]}}}}
```

## Duplicate map

> Create a copy of a map with all its layers, elements, and configuration.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Maps are the centerpiece of Felt.\n\nWith these APIs, you can create, retrieve, update, delete, move, and duplicate maps programmatically.\n","name":"Maps"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"MapDuplicateParams":{"additionalProperties":false,"properties":{"destination":{"oneOf":[{"additionalProperties":false,"properties":{"project_id":{"$ref":"#/components/schemas/FeltID"}},"required":["project_id"],"type":"object"},{"additionalProperties":false,"properties":{"folder_id":{"$ref":"#/components/schemas/FeltID"}},"required":["folder_id"],"type":"object"}],"type":"object"},"title":{"description":"Title for the duplicated map. If not provided, will default to '[Original Title] (copy)'","type":"string"}},"title":"MapDuplicateParams","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"Map":{"additionalProperties":false,"properties":{"basemap":{"type":"string"},"created_at":{"format":"date_time","type":"string"},"element_groups":{"items":{"properties":{"elements":{"$ref":"#/components/schemas/GeoJSON"},"id":{"$ref":"#/components/schemas/FeltID"},"name":{"nullable":true,"type":"string"}},"type":"object"},"type":"array"},"elements":{"$ref":"#/components/schemas/GeoJSON"},"folder_id":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layer_groups":{"items":{"$ref":"#/components/schemas/LayerGroup"},"type":"array"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"project_id":{"nullable":false,"type":"string"},"public_access":{"enum":["private","view_only","view_and_comment","view_comment_and_edit"],"type":"string"},"table_settings":{"$ref":"#/components/schemas/MapTableSettings"},"thumbnail_url":{"description":"A static thumbnail image of the map","nullable":true,"type":"string"},"title":{"type":"string"},"type":{"enum":["map"],"type":"string"},"url":{"type":"string"},"viewer_permissions":{"$ref":"#/components/schemas/MapViewerPermissions"},"visited_at":{"format":"date_time","nullable":true,"type":"string"}},"required":["id","type","url","title","thumbnail_url","created_at","visited_at","layers","layer_groups","elements","element_groups","project_id","public_access"],"title":"Map","type":"object"},"GeoJSON":{"properties":{"features":{"items":{"properties":{"geometry":{"properties":{"felt:id":{"$ref":"#/components/schemas/FeltID"},"felt:parentId":{"format":"felt_id","nullable":true,"type":"string"}},"type":"object"},"properties":{"type":"object"},"type":{"enum":["Feature"],"type":"string"}},"type":"object"},"type":"array"},"type":{"enum":["FeatureCollection"],"type":"string"}},"required":["type","features"],"title":"GeoJSON","type":"object"},"LayerGroup":{"additionalProperties":false,"properties":{"caption":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"legend_visibility":{"description":"Controls how the layer group is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"nullable":false,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":false,"type":"integer"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"type":{"enum":["layer_group"],"type":"string"},"visibility_interaction":{"description":"Controls how the layer group is displayed in the legend. Defaults to `\"default\"`.","enum":["default","slider","select","multi_select"],"nullable":false,"type":"string"}},"required":["id","type","name","caption","visibility_interaction","layers"],"title":"LayerGroup","type":"object"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"MapTableSettings":{"additionalProperties":false,"properties":{"default_table_layer_id":{"format":"felt_id","nullable":true,"type":"string"},"viewers_can_open_table":{"description":"Whether viewers can open the data table","type":"boolean"}},"title":"MapTableSettings","type":"object"},"MapViewerPermissions":{"additionalProperties":false,"properties":{"can_duplicate_map":{"description":"Whether viewers can duplicate the map and data","type":"boolean"},"can_export_data":{"description":"Whether viewers can export map data","type":"boolean"},"can_see_map_presence":{"description":"Whether viewers can see who else is viewing the map","type":"boolean"}},"title":"MapViewerPermissions","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/duplicate":{"post":{"callbacks":{},"description":"Create a copy of a map with all its layers, elements, and configuration.","operationId":"duplicate_map","parameters":[{"description":"The ID of the map to duplicate","in":"path","name":"map_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MapDuplicateParams"}}},"description":"Map duplicate params","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Map"}}},"description":"Duplicated Map"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Duplicate map","tags":["Maps"]}}}}
```

## Create map

> Create a new map with optional customization options.\
> \
> Several aspects can be customized when creating a new map, including:\
> \
> \* Title\
> \* Initial location (latitude, longitude and zoom level)\
> \* Sharing permissions (defaults to viewing and commenting for users with the map URL)\
> \* An array of URLs to import on map creation<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Maps are the centerpiece of Felt.\n\nWith these APIs, you can create, retrieve, update, delete, move, and duplicate maps programmatically.\n","name":"Maps"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"MapCreateParams":{"additionalProperties":false,"properties":{"basemap":{"description":"The basemap to use for the new map. Defaults to \"default\". Valid values are \"default\", \"light\", \"dark\", \"satellite\", a valid raster tile URL with {x}, {y}, and {z} parameters, or a hex color string like #ff0000.","type":"string"},"description":{"description":"A description to display in the map legend","type":"string"},"lat":{"description":"If no data has been uploaded to the map, the initial latitude to center the map display on.","type":"number"},"layer_urls":{"description":"An array of urls to use to create layers in the map. Only tile URLs for raster layers are supported at the moment.","items":{"type":"string"},"type":"array"},"lon":{"description":"If no data has been uploaded to the map, the initial longitude to center the map display on.","type":"number"},"public_access":{"description":"The level of access to grant to the map. Defaults to \"view_only\".","enum":["private","view_only","view_and_comment","view_comment_and_edit"],"type":"string"},"title":{"description":"The title to be used for the map. Defaults to \"Untitled Map\"","type":"string"},"workspace_id":{"description":"The workspace to create the map in. Defaults to the latest used workspace","type":"string"},"zoom":{"description":"If no data has been uploaded to the map, the initial zoom level for the map to display.","type":"number"}},"title":"MapCreateParams","type":"object"},"Map":{"additionalProperties":false,"properties":{"basemap":{"type":"string"},"created_at":{"format":"date_time","type":"string"},"element_groups":{"items":{"properties":{"elements":{"$ref":"#/components/schemas/GeoJSON"},"id":{"$ref":"#/components/schemas/FeltID"},"name":{"nullable":true,"type":"string"}},"type":"object"},"type":"array"},"elements":{"$ref":"#/components/schemas/GeoJSON"},"folder_id":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layer_groups":{"items":{"$ref":"#/components/schemas/LayerGroup"},"type":"array"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"project_id":{"nullable":false,"type":"string"},"public_access":{"enum":["private","view_only","view_and_comment","view_comment_and_edit"],"type":"string"},"table_settings":{"$ref":"#/components/schemas/MapTableSettings"},"thumbnail_url":{"description":"A static thumbnail image of the map","nullable":true,"type":"string"},"title":{"type":"string"},"type":{"enum":["map"],"type":"string"},"url":{"type":"string"},"viewer_permissions":{"$ref":"#/components/schemas/MapViewerPermissions"},"visited_at":{"format":"date_time","nullable":true,"type":"string"}},"required":["id","type","url","title","thumbnail_url","created_at","visited_at","layers","layer_groups","elements","element_groups","project_id","public_access"],"title":"Map","type":"object"},"GeoJSON":{"properties":{"features":{"items":{"properties":{"geometry":{"properties":{"felt:id":{"$ref":"#/components/schemas/FeltID"},"felt:parentId":{"format":"felt_id","nullable":true,"type":"string"}},"type":"object"},"properties":{"type":"object"},"type":{"enum":["Feature"],"type":"string"}},"type":"object"},"type":"array"},"type":{"enum":["FeatureCollection"],"type":"string"}},"required":["type","features"],"title":"GeoJSON","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"LayerGroup":{"additionalProperties":false,"properties":{"caption":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"legend_visibility":{"description":"Controls how the layer group is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"nullable":false,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":false,"type":"integer"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"type":{"enum":["layer_group"],"type":"string"},"visibility_interaction":{"description":"Controls how the layer group is displayed in the legend. Defaults to `\"default\"`.","enum":["default","slider","select","multi_select"],"nullable":false,"type":"string"}},"required":["id","type","name","caption","visibility_interaction","layers"],"title":"LayerGroup","type":"object"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"MapTableSettings":{"additionalProperties":false,"properties":{"default_table_layer_id":{"format":"felt_id","nullable":true,"type":"string"},"viewers_can_open_table":{"description":"Whether viewers can open the data table","type":"boolean"}},"title":"MapTableSettings","type":"object"},"MapViewerPermissions":{"additionalProperties":false,"properties":{"can_duplicate_map":{"description":"Whether viewers can duplicate the map and data","type":"boolean"},"can_export_data":{"description":"Whether viewers can export map data","type":"boolean"},"can_see_map_presence":{"description":"Whether viewers can see who else is viewing the map","type":"boolean"}},"title":"MapViewerPermissions","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps":{"post":{"callbacks":{},"description":"Create a new map with optional customization options.\n\nSeveral aspects can be customized when creating a new map, including:\n\n* Title\n* Initial location (latitude, longitude and zoom level)\n* Sharing permissions (defaults to viewing and commenting for users with the map URL)\n* An array of URLs to import on map creation\n","operationId":"create_map","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MapCreateParams"}}},"description":"Map create params","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Map"}}},"description":"Map"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Create map","tags":["Maps"]}}}}
```

## Update map

> Update map properties including title, description, and access permissions.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Maps are the centerpiece of Felt.\n\nWith these APIs, you can create, retrieve, update, delete, move, and duplicate maps programmatically.\n","name":"Maps"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"MapUpdateParams":{"additionalProperties":false,"properties":{"basemap":{"description":"The basemap to use for the map. Defaults to \"default\". Valid values are \"default\", \"light\", \"dark\", \"satellite\", a valid raster tile URL with {x}, {y}, and {z} parameters, or a hex color string like #ff0000.","type":"string"},"description":{"description":"A description to display in the map legend","type":"string"},"public_access":{"description":"The level of access to grant to the map. Defaults to \"view_only\".","enum":["private","view_only","view_and_comment","view_comment_and_edit"],"type":"string"},"table_settings":{"$ref":"#/components/schemas/MapTableSettings"},"title":{"description":"The new title for the map","type":"string"},"viewer_permissions":{"$ref":"#/components/schemas/MapViewerPermissions"}},"title":"MapUpdateParams","type":"object"},"MapTableSettings":{"additionalProperties":false,"properties":{"default_table_layer_id":{"format":"felt_id","nullable":true,"type":"string"},"viewers_can_open_table":{"description":"Whether viewers can open the data table","type":"boolean"}},"title":"MapTableSettings","type":"object"},"MapViewerPermissions":{"additionalProperties":false,"properties":{"can_duplicate_map":{"description":"Whether viewers can duplicate the map and data","type":"boolean"},"can_export_data":{"description":"Whether viewers can export map data","type":"boolean"},"can_see_map_presence":{"description":"Whether viewers can see who else is viewing the map","type":"boolean"}},"title":"MapViewerPermissions","type":"object"},"Map":{"additionalProperties":false,"properties":{"basemap":{"type":"string"},"created_at":{"format":"date_time","type":"string"},"element_groups":{"items":{"properties":{"elements":{"$ref":"#/components/schemas/GeoJSON"},"id":{"$ref":"#/components/schemas/FeltID"},"name":{"nullable":true,"type":"string"}},"type":"object"},"type":"array"},"elements":{"$ref":"#/components/schemas/GeoJSON"},"folder_id":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layer_groups":{"items":{"$ref":"#/components/schemas/LayerGroup"},"type":"array"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"project_id":{"nullable":false,"type":"string"},"public_access":{"enum":["private","view_only","view_and_comment","view_comment_and_edit"],"type":"string"},"table_settings":{"$ref":"#/components/schemas/MapTableSettings"},"thumbnail_url":{"description":"A static thumbnail image of the map","nullable":true,"type":"string"},"title":{"type":"string"},"type":{"enum":["map"],"type":"string"},"url":{"type":"string"},"viewer_permissions":{"$ref":"#/components/schemas/MapViewerPermissions"},"visited_at":{"format":"date_time","nullable":true,"type":"string"}},"required":["id","type","url","title","thumbnail_url","created_at","visited_at","layers","layer_groups","elements","element_groups","project_id","public_access"],"title":"Map","type":"object"},"GeoJSON":{"properties":{"features":{"items":{"properties":{"geometry":{"properties":{"felt:id":{"$ref":"#/components/schemas/FeltID"},"felt:parentId":{"format":"felt_id","nullable":true,"type":"string"}},"type":"object"},"properties":{"type":"object"},"type":{"enum":["Feature"],"type":"string"}},"type":"object"},"type":"array"},"type":{"enum":["FeatureCollection"],"type":"string"}},"required":["type","features"],"title":"GeoJSON","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"LayerGroup":{"additionalProperties":false,"properties":{"caption":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"legend_visibility":{"description":"Controls how the layer group is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"nullable":false,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":false,"type":"integer"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"type":{"enum":["layer_group"],"type":"string"},"visibility_interaction":{"description":"Controls how the layer group is displayed in the legend. Defaults to `\"default\"`.","enum":["default","slider","select","multi_select"],"nullable":false,"type":"string"}},"required":["id","type","name","caption","visibility_interaction","layers"],"title":"LayerGroup","type":"object"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/update":{"post":{"callbacks":{},"description":"Update map properties including title, description, and access permissions.","operationId":"update_map","parameters":[{"description":"The ID of the map to update","in":"path","name":"map_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MapUpdateParams"}}},"description":"Map update params","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Map"}}},"description":"Map"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Update map","tags":["Maps"]}}}}
```

## Get map

> Retrieve a map with its metadata including title, URL, thumbnail, and timestamps.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Maps are the centerpiece of Felt.\n\nWith these APIs, you can create, retrieve, update, delete, move, and duplicate maps programmatically.\n","name":"Maps"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"Map":{"additionalProperties":false,"properties":{"basemap":{"type":"string"},"created_at":{"format":"date_time","type":"string"},"element_groups":{"items":{"properties":{"elements":{"$ref":"#/components/schemas/GeoJSON"},"id":{"$ref":"#/components/schemas/FeltID"},"name":{"nullable":true,"type":"string"}},"type":"object"},"type":"array"},"elements":{"$ref":"#/components/schemas/GeoJSON"},"folder_id":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layer_groups":{"items":{"$ref":"#/components/schemas/LayerGroup"},"type":"array"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"project_id":{"nullable":false,"type":"string"},"public_access":{"enum":["private","view_only","view_and_comment","view_comment_and_edit"],"type":"string"},"table_settings":{"$ref":"#/components/schemas/MapTableSettings"},"thumbnail_url":{"description":"A static thumbnail image of the map","nullable":true,"type":"string"},"title":{"type":"string"},"type":{"enum":["map"],"type":"string"},"url":{"type":"string"},"viewer_permissions":{"$ref":"#/components/schemas/MapViewerPermissions"},"visited_at":{"format":"date_time","nullable":true,"type":"string"}},"required":["id","type","url","title","thumbnail_url","created_at","visited_at","layers","layer_groups","elements","element_groups","project_id","public_access"],"title":"Map","type":"object"},"GeoJSON":{"properties":{"features":{"items":{"properties":{"geometry":{"properties":{"felt:id":{"$ref":"#/components/schemas/FeltID"},"felt:parentId":{"format":"felt_id","nullable":true,"type":"string"}},"type":"object"},"properties":{"type":"object"},"type":{"enum":["Feature"],"type":"string"}},"type":"object"},"type":"array"},"type":{"enum":["FeatureCollection"],"type":"string"}},"required":["type","features"],"title":"GeoJSON","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"LayerGroup":{"additionalProperties":false,"properties":{"caption":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"legend_visibility":{"description":"Controls how the layer group is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"nullable":false,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":false,"type":"integer"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"type":{"enum":["layer_group"],"type":"string"},"visibility_interaction":{"description":"Controls how the layer group is displayed in the legend. Defaults to `\"default\"`.","enum":["default","slider","select","multi_select"],"nullable":false,"type":"string"}},"required":["id","type","name","caption","visibility_interaction","layers"],"title":"LayerGroup","type":"object"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"MapTableSettings":{"additionalProperties":false,"properties":{"default_table_layer_id":{"format":"felt_id","nullable":true,"type":"string"},"viewers_can_open_table":{"description":"Whether viewers can open the data table","type":"boolean"}},"title":"MapTableSettings","type":"object"},"MapViewerPermissions":{"additionalProperties":false,"properties":{"can_duplicate_map":{"description":"Whether viewers can duplicate the map and data","type":"boolean"},"can_export_data":{"description":"Whether viewers can export map data","type":"boolean"},"can_see_map_presence":{"description":"Whether viewers can see who else is viewing the map","type":"boolean"}},"title":"MapViewerPermissions","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}":{"get":{"callbacks":{},"description":"Retrieve a map with its metadata including title, URL, thumbnail, and timestamps.","operationId":"show_map","parameters":[{"description":"","in":"path","name":"map_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Map"}}},"description":"Map"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Get map","tags":["Maps"]}}}}
```

## Delete map

> Permanently delete a map and all its associated data.\
> \
> {% hint style="warning" %}\
> This action cannot be undone. The map and all its layers, elements, and comments will be permanently removed.\
> {% endhint %}<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Maps are the centerpiece of Felt.\n\nWith these APIs, you can create, retrieve, update, delete, move, and duplicate maps programmatically.\n","name":"Maps"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}":{"delete":{"callbacks":{},"description":"Permanently delete a map and all its associated data.\n\n{% hint style=\"warning\" %}\nThis action cannot be undone. The map and all its layers, elements, and comments will be permanently removed.\n{% endhint %}\n","operationId":"delete_map","parameters":[{"description":"The ID of the map to delete","in":"path","name":"map_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Delete map","tags":["Maps"]}}}}
```


# Layers

APIs to visualize spatial data

Layers enable you to visualize, style and interact with your spatial data.

With these APIs, you can upload data, manage layer styling, publish and refresh live data layers.

## Get map layer group

> Retrieve detailed information about a specific layer group including its layers and configuration.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Layers enable you to visualize, style and interact with your spatial data.\n\nWith these APIs, you can upload data, manage layer styling, publish and refresh live data layers.\n","name":"Layers"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"LayerGroup":{"additionalProperties":false,"properties":{"caption":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"legend_visibility":{"description":"Controls how the layer group is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"nullable":false,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":false,"type":"integer"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"type":{"enum":["layer_group"],"type":"string"},"visibility_interaction":{"description":"Controls how the layer group is displayed in the legend. Defaults to `\"default\"`.","enum":["default","slider","select","multi_select"],"nullable":false,"type":"string"}},"required":["id","type","name","caption","visibility_interaction","layers"],"title":"LayerGroup","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layer_groups/{layer_group_id}":{"get":{"callbacks":{},"description":"Retrieve detailed information about a specific layer group including its layers and configuration.","operationId":"show_map_layer_group","parameters":[{"description":"","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"","in":"path","name":"layer_group_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerGroup"}}},"description":"Layer Group"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Get map layer group","tags":["Layers"]}}}}
```

## Update map layer group

> Update layer group properties including name, visibility, and organization settings.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Layers enable you to visualize, style and interact with your spatial data.\n\nWith these APIs, you can upload data, manage layer styling, publish and refresh live data layers.\n","name":"Layers"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"LayerGroupUpdateParams":{"additionalProperties":false,"properties":{"caption":{"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"legend_visibility":{"description":"Controls how the layer group is displayed in the legend","enum":["hide","show"],"type":"string"},"name":{"type":"string"},"ordering_key":{"type":"integer"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","type":"string"},"visibility_interaction":{"description":"Controls how the layer group is displayed in the legend. Defaults to `\"default\"`.","enum":["default","slider","select","multi_select"],"nullable":true,"type":"string"}},"title":"LayerGroupUpdateParams","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"LayerGroup":{"additionalProperties":false,"properties":{"caption":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"legend_visibility":{"description":"Controls how the layer group is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"nullable":false,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":false,"type":"integer"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"type":{"enum":["layer_group"],"type":"string"},"visibility_interaction":{"description":"Controls how the layer group is displayed in the legend. Defaults to `\"default\"`.","enum":["default","slider","select","multi_select"],"nullable":false,"type":"string"}},"required":["id","type","name","caption","visibility_interaction","layers"],"title":"LayerGroup","type":"object"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layer_groups/{layer_group_id}":{"post":{"callbacks":{},"description":"Update layer group properties including name, visibility, and organization settings.","operationId":"update_map_layer_group","parameters":[{"description":"","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"","in":"path","name":"layer_group_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerGroupUpdateParams"}}},"description":"LayerGroup parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerGroup"}}},"description":"LayerGroup"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Update map layer group","tags":["Layers"]}}}}
```

## Delete map layer group

> Permanently remove a layer group and all its contained layers from a map.\
> \
> {% hint style="warning" %}\
> This action cannot be undone. The layer group and all its contained layers will be permanently removed from the map.\
> {% endhint %}<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Layers enable you to visualize, style and interact with your spatial data.\n\nWith these APIs, you can upload data, manage layer styling, publish and refresh live data layers.\n","name":"Layers"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layer_groups/{layer_group_id}":{"delete":{"callbacks":{},"description":"Permanently remove a layer group and all its contained layers from a map.\n\n{% hint style=\"warning\" %}\nThis action cannot be undone. The layer group and all its contained layers will be permanently removed from the map.\n{% endhint %}\n","operationId":"delete_map_layer_group","parameters":[{"description":"The ID of the map to delete the layer group from","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer group to delete","in":"path","name":"layer_group_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Delete map layer group","tags":["Layers"]}}}}
```

## List map layers

> Retrieve all layers from a map, including uploaded files and connected data sources.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Layers enable you to visualize, style and interact with your spatial data.\n\nWith these APIs, you can upload data, manage layer styling, publish and refresh live data layers.\n","name":"Layers"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"LayerList":{"items":{"$ref":"#/components/schemas/Layer"},"title":"LayerList","type":"array"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layers":{"get":{"callbacks":{},"description":"Retrieve all layers from a map, including uploaded files and connected data sources.","operationId":"list_map_layers","parameters":[{"description":"","in":"path","name":"map_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerList"}}},"description":"Layers list"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"List map layers","tags":["Layers"]}}}}
```

## Update map layer

> Update layer properties including styling, visibility, grouping, and other configuration options.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Layers enable you to visualize, style and interact with your spatial data.\n\nWith these APIs, you can upload data, manage layer styling, publish and refresh live data layers.\n","name":"Layers"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"LayerUpdateParamsList":{"items":{"$ref":"#/components/schemas/LayerUpdateParams"},"title":"LayerUpdateParamsList","type":"array"},"LayerUpdateParams":{"additionalProperties":false,"properties":{"caption":{"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layer_group_id":{"format":"felt_id","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend.","enum":["hide","show"],"type":"string"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"type":"string"},"ordering_key":{"type":"integer"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","type":"string"}},"required":["id"],"title":"LayerUpdateParams","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"LayerList":{"items":{"$ref":"#/components/schemas/Layer"},"title":"LayerList","type":"array"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layers":{"post":{"callbacks":{},"description":"Update layer properties including styling, visibility, grouping, and other configuration options.","operationId":"update_map_layer","parameters":[{"description":"","in":"path","name":"map_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerUpdateParamsList"}}},"description":"Layer params list","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerList"}}},"description":"Layer list"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Update map layer","tags":["Layers"]}}}}
```

## List map layer groups

> Retrieve all layer groups from a map to see how layers are organized.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Layers enable you to visualize, style and interact with your spatial data.\n\nWith these APIs, you can upload data, manage layer styling, publish and refresh live data layers.\n","name":"Layers"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"LayerGroupList":{"items":{"$ref":"#/components/schemas/LayerGroup"},"title":"LayerGroupList","type":"array"},"LayerGroup":{"additionalProperties":false,"properties":{"caption":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"legend_visibility":{"description":"Controls how the layer group is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"nullable":false,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":false,"type":"integer"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"type":{"enum":["layer_group"],"type":"string"},"visibility_interaction":{"description":"Controls how the layer group is displayed in the legend. Defaults to `\"default\"`.","enum":["default","slider","select","multi_select"],"nullable":false,"type":"string"}},"required":["id","type","name","caption","visibility_interaction","layers"],"title":"LayerGroup","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layer_groups":{"get":{"callbacks":{},"description":"Retrieve all layer groups from a map to see how layers are organized.","operationId":"list_map_layer_groups","parameters":[{"description":"","in":"path","name":"map_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerGroupList"}}},"description":"Layers Groups"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"List map layer groups","tags":["Layers"]}}}}
```

## Update map layer groups

> Update properties for multiple layer groups in a single request for efficient bulk operations.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Layers enable you to visualize, style and interact with your spatial data.\n\nWith these APIs, you can upload data, manage layer styling, publish and refresh live data layers.\n","name":"Layers"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"LayerGroupParamsList":{"items":{"$ref":"#/components/schemas/LayerGroupParams"},"title":"LayerGroupParamsList","type":"array"},"LayerGroupParams":{"additionalProperties":false,"properties":{"caption":{"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"legend_visibility":{"description":"Controls how the layer group is displayed in the legend","enum":["hide","show"],"type":"string"},"name":{"type":"string"},"ordering_key":{"type":"integer"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","type":"string"},"visibility_interaction":{"description":"Controls how the layer group is displayed in the legend. Defaults to `\"default\"`.","enum":["default","slider","select","multi_select"],"nullable":true,"type":"string"}},"required":["name"],"title":"LayerGroupParams","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"LayerGroupList":{"items":{"$ref":"#/components/schemas/LayerGroup"},"title":"LayerGroupList","type":"array"},"LayerGroup":{"additionalProperties":false,"properties":{"caption":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"legend_visibility":{"description":"Controls how the layer group is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"nullable":false,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":false,"type":"integer"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"type":{"enum":["layer_group"],"type":"string"},"visibility_interaction":{"description":"Controls how the layer group is displayed in the legend. Defaults to `\"default\"`.","enum":["default","slider","select","multi_select"],"nullable":false,"type":"string"}},"required":["id","type","name","caption","visibility_interaction","layers"],"title":"LayerGroup","type":"object"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layer_groups":{"post":{"callbacks":{},"description":"Update properties for multiple layer groups in a single request for efficient bulk operations.","operationId":"update_map_layer_groups","parameters":[{"description":"","in":"path","name":"map_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerGroupParamsList"}}},"description":"LayerGroup parameters list","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerGroupList"}}},"description":"LayerGroup list"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Update map layer groups","tags":["Layers"]}}}}
```

## Update layer style

> Update the visual styling properties of a layer including colors, symbols, and rendering options.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Layers enable you to visualize, style and interact with your spatial data.\n\nWith these APIs, you can upload data, manage layer styling, publish and refresh live data layers.\n","name":"Layers"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"LayerUpdateStyleParams":{"properties":{"style":{"description":"The new layer style, specified in Felt Style Language format","type":"object"}},"required":["style"],"title":"LayerUpdateStyleParams","type":"object"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layers/{layer_id}/update_style":{"post":{"callbacks":{},"description":"Update the visual styling properties of a layer including colors, symbols, and rendering options.","operationId":"update_map_layer_style","parameters":[{"description":"The ID of the map where the layer is located","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer to update the style of","in":"path","name":"layer_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerUpdateStyleParams"}}},"description":"Layer style parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Layer"}}},"description":"Layer"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Update layer style","tags":["Layers"]}}}}
```

## Get map layer

> Retrieve detailed information about a specific layer including data source, styling, and configuration.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Layers enable you to visualize, style and interact with your spatial data.\n\nWith these APIs, you can upload data, manage layer styling, publish and refresh live data layers.\n","name":"Layers"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layers/{layer_id}":{"get":{"callbacks":{},"description":"Retrieve detailed information about a specific layer including data source, styling, and configuration.","operationId":"show_map_layer","parameters":[{"description":"","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"","in":"path","name":"layer_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Layer"}}},"description":"Layer"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Get map layer","tags":["Layers"]}}}}
```

## Delete map layer

> Permanently remove a layer from a map.\
> \
> {% hint style="warning" %}\
> This action cannot be undone. The layer and all its data will be permanently removed from the map.\
> {% endhint %}<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Layers enable you to visualize, style and interact with your spatial data.\n\nWith these APIs, you can upload data, manage layer styling, publish and refresh live data layers.\n","name":"Layers"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layers/{layer_id}":{"delete":{"callbacks":{},"description":"Permanently remove a layer from a map.\n\n{% hint style=\"warning\" %}\nThis action cannot be undone. The layer and all its data will be permanently removed from the map.\n{% endhint %}\n","operationId":"delete_map_layer","parameters":[{"description":"The ID of the map to delete the layer from","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer to delete","in":"path","name":"layer_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Delete map layer","tags":["Layers"]}}}}
```

## Duplicate map layers

> Copy layers or layer groups to other maps, preserving styling and configuration.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Layers enable you to visualize, style and interact with your spatial data.\n\nWith these APIs, you can upload data, manage layer styling, publish and refresh live data layers.\n","name":"Layers"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"DuplicateLayersParams":{"items":{"oneOf":[{"properties":{"destination_map_id":{"$ref":"#/components/schemas/FeltID"},"source_layer_id":{"$ref":"#/components/schemas/FeltID"}},"required":["source_layer_id","destination_map_id"],"title":"DuplicateLayersParams-Layer","type":"object"},{"properties":{"destination_map_id":{"$ref":"#/components/schemas/FeltID"},"source_layer_group_id":{"$ref":"#/components/schemas/FeltID"}},"required":["source_layer_group_id","destination_map_id"],"title":"DuplicateLayersParams-LayerGroup","type":"object"}]},"title":"DuplicateLayersParams","type":"array"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"DuplicateLayersResponse":{"properties":{"layer_groups":{"items":{"$ref":"#/components/schemas/LayerGroup"},"type":"array"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"}},"required":["layers","layer_groups"],"title":"DuplicateLayersResponse","type":"object"},"LayerGroup":{"additionalProperties":false,"properties":{"caption":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"legend_visibility":{"description":"Controls how the layer group is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"nullable":false,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":false,"type":"integer"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"type":{"enum":["layer_group"],"type":"string"},"visibility_interaction":{"description":"Controls how the layer group is displayed in the legend. Defaults to `\"default\"`.","enum":["default","slider","select","multi_select"],"nullable":false,"type":"string"}},"required":["id","type","name","caption","visibility_interaction","layers"],"title":"LayerGroup","type":"object"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/duplicate_layers":{"post":{"callbacks":{},"description":"Copy layers or layer groups to other maps, preserving styling and configuration.","operationId":"duplicate_map_layers","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuplicateLayersParams"}}},"description":"Duplicate Layers Params","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuplicateLayersResponse"}}},"description":"Duplicate Layers Response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Duplicate map layers","tags":["Layers"]}}}}
```


# Layer Uploads

APIs to upload data

With these APIs, you can upload your data to create new layers.

## Add layer from data source

> Create a new layer from an existing data source connection (database, API, or file).

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"With these APIs, you can upload your data to create new layers.\n","name":"Layer Uploads"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"AddSourceLayerParams":{"oneOf":[{"properties":{"dataset_id":{"$ref":"#/components/schemas/FeltID"},"from":{"enum":["dataset"],"type":"string"}},"required":["from","dataset_id"],"title":"AddSourceLayer-Dataset-Params","type":"object"},{"properties":{"from":{"enum":["sql"],"type":"string"},"query":{"type":"string"},"source_id":{"$ref":"#/components/schemas/FeltID"}},"required":["from","source_id","query"],"title":"AddSourceLayer-SQL-Params","type":"object"},{"properties":{"from":{"enum":["stac"],"type":"string"},"source_id":{"$ref":"#/components/schemas/FeltID"},"stac_asset_url":{"type":"string"}},"required":["from","source_id","stac_asset_url"],"title":"AddSourceLayer-STAC-Params","type":"object"}],"title":"AddSourceLayerParams","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"AddSourceLayerAccepted":{"additionalProperties":false,"properties":{"links":{"additionalProperties":false,"properties":{"layer_group":{"type":"string"},"self":{"type":"string"}},"type":"object"},"status":{"enum":["accepted"],"type":"string"}},"title":"AddSourceLayerAccepted","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/add_source_layer":{"post":{"callbacks":{},"description":"Create a new layer from an existing data source connection (database, API, or file).","operationId":"add_map_layer_from_source","parameters":[{"description":"","in":"path","name":"map_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSourceLayerParams"}}},"description":"AddSourceLayerParams","required":false},"responses":{"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSourceLayerAccepted"}}},"description":"AddSourceLayerAccepted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Add layer from data source","tags":["Layer Uploads"]}}}}
```

## Refresh map layer

> Trigger a data refresh for a layer from its original data source to pull in the latest updates.\
> \
> After uploading a file or URL, you may want to update the resulting layer with new data. The process is quite similar to the upload:\
> \
> \* For URL uploads, simply making a single \`POST\` request to the refresh endpoint is enough\
> \* For file refreshes, the response of the initial \`POST\` request will include a URL and some pre-signed attributes, which will be used to upload the new file to Amazon S3.\
> \
> {% hint style="info" %}\
> With the \`felt\_python\` library, you can refresh a layer with a simple function call:\
> {% endhint %}\
> \
> \`\`\`python\
> from felt\_python import (refresh\_file\_layer, refresh\_url\_layer)\
> \
> refresh\_file\_layer(map\_id, layer\_id, file\_name="features.geojson")\
> refresh\_url\_layer(map\_id, layer\_id)\
> \`\`\`<br>

````json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"With these APIs, you can upload your data to create new layers.\n","name":"Layer Uploads"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"UploadResponse":{"properties":{"layer_group_id":{"$ref":"#/components/schemas/FeltID"},"layer_id":{"description":"The ID of the layer created by this upload. If multiple layers are included in the upload, this is the ID of the first layer in the layer group.","format":"felt_id","nullable":false,"type":"string"},"presigned_attributes":{"description":"If provided, the presigned attributes to attach to the post request","nullable":true,"type":"object"},"type":{"enum":["upload_response"],"type":"string"},"url":{"description":"If provided, the URL to post the file to","nullable":true,"type":"string"}},"title":"UploadResponse","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layers/{layer_id}/refresh":{"post":{"callbacks":{},"description":"Trigger a data refresh for a layer from its original data source to pull in the latest updates.\n\nAfter uploading a file or URL, you may want to update the resulting layer with new data. The process is quite similar to the upload:\n\n* For URL uploads, simply making a single `POST` request to the refresh endpoint is enough\n* For file refreshes, the response of the initial `POST` request will include a URL and some pre-signed attributes, which will be used to upload the new file to Amazon S3.\n\n{% hint style=\"info\" %}\nWith the `felt_python` library, you can refresh a layer with a simple function call:\n{% endhint %}\n\n```python\nfrom felt_python import (refresh_file_layer, refresh_url_layer)\n\nrefresh_file_layer(map_id, layer_id, file_name=\"features.geojson\")\nrefresh_url_layer(map_id, layer_id)\n```\n","operationId":"refresh_map_layer","parameters":[{"description":"The ID of the map hosting the layer to refresh","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer to refresh","in":"path","name":"layer_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadResponse"}}},"description":"Refresh response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Refresh map layer","tags":["Layer Uploads"]}}}}
````

## Upload map layer

> Upload a file or import data from a URL to create a new layer on the map.\
> \
> The \`/upload\` endpoint can be used for both URL and file uploads:\
> \
> \* For URL uploads, simply making a single \`POST\` request to the upload endpoint is enough\
> \* For file uploads, the response of the initial \`POST\` request contain information you will use to upload the file to Amazon S3\
> \
> Check our \[Upload Anything]\(<https://help.felt.com/upload-anything>) docs to see what URLs are supported.\
> \
> \#### \*\*Uploading the file to Amazon S3\*\*\
> \
> Layer files aren't uploaded directly to the Felt API. Instead, they are uploaded to an S3 bucket.\
> \
> The response to this API request will include a URL and pre-signed params for you to use to upload your file. Only a single file may be uploaded — if you wish to upload several files at once, consider wrapping them in a zip file.\
> \
> To upload the file, you must perform a multipart upload, and include the file contents in the \`file\` field.\
> \
> {% hint style="info" %}\
> With the \`felt\_python\` library, you can upload a file with a simple function call:\
> {% endhint %}\
> \
> \`\`\`python\
> from felt\_python import upload\_file\
> \
> upload\_file(map\_id, file\_name="features.geojson", layer\_name="My new layer")\
> \`\`\`<br>

````json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"With these APIs, you can upload your data to create new layers.\n","name":"Layer Uploads"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"UploadLayerParams":{"properties":{"hints":{"description":"A list of hints for interpreting the data in the upload.","items":{"$ref":"#/components/schemas/UploadInterpretationHint"},"type":"array"},"import_url":{"description":"A public URL containing geodata to import, in place of uploading a file.","type":"string"},"lat":{"description":"(Image uploads only) The latitude of the image center.","type":"number"},"lng":{"description":"(Image uploads only) The longitude of the image center.","type":"number"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"description":"The display name for the new layer.","type":"string"},"zoom":{"description":"(Image uploads only) The zoom level of the image.","type":"number"}},"required":["name"],"title":"UploadLayerParams","type":"object"},"UploadInterpretationHint":{"oneOf":[{"description":"A hint that the data contains Latitude and Longitude in two individual attributes.","properties":{"attributes":{"additionalProperties":false,"properties":{"lat":{"type":"string"},"lng":{"type":"string"}},"required":["lat","lng"],"type":"object"}},"required":["attributes"],"title":"UploadInterpretationHint-AttributeHint-LatitudeAndLongitude","type":"object"},{"description":"A hint that the data contains Latitude and Longitude combined in a single attribute.","properties":{"attribute":{"additionalProperties":false,"properties":{"lat_lng":{"type":"string"}},"required":["lat_lng"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-LatitudeAndLongitudeCombined","type":"object"},{"description":"A hint that the data contains a full Address contained in a single attribute.","properties":{"attribute":{"properties":{"full_address":{"type":"string"}},"required":["full_address"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-FullAddress","type":"object"},{"description":"A hint that the data contains an Address spread out over multiple attributes.","properties":{"attributes":{"additionalProperties":false,"properties":{"country":{"description":"ex: USA","type":"string"},"locality":{"description":"ex: Oakland","type":"string"},"postal_code":{"description":"ex: 94612","type":"string"},"region":{"description":"ex: California","type":"string"},"street_address":{"description":"ex: 1904 Franklin St","type":"string"}},"required":["street_address"],"type":"object"}},"required":["attributes"],"title":"UploadInterpretationHint-AttributeHint-PartialAddress","type":"object"},{"description":"A hint that the data contains a Locality spread out over multiple attributes.","properties":{"attributes":{"additionalProperties":false,"properties":{"country":{"description":"ex: USA","type":"string"},"locality":{"description":"ex: Oakland","type":"string"},"region":{"description":"ex: California","type":"string"}},"required":["locality"],"type":"object"}},"required":["attributes"],"title":"UploadInterpretationHint-AttributeHint-Locality","type":"object"},{"description":"A hint that the data contains a WKT/WKB Literal attribute","properties":{"attribute":{"additionalProperties":false,"properties":{"wkt_wkb_literal":{"type":"string"}},"required":["wkt_wkb_literal"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-WKT_WKB_Literal","type":"object"},{"description":"A hint that the data contains a US Census Tract (Census 2020) attribute. https://www.census.gov/geographies/reference-maps/2020/geo/2020pl-maps/2020-census-tract.html","properties":{"attribute":{"additionalProperties":false,"properties":{"us_census_tract_2020":{"type":"string"}},"required":["us_census_tract_2020"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-US_Census_Tract2020","type":"object"},{"description":"A hint that the data contains a US Core-based statistical area (Census 2020) attribute: https://www.census.gov/geographies/reference-maps/2020/geo/cbsa.html","properties":{"attribute":{"additionalProperties":false,"properties":{"us_cbsa_2020":{"type":"string"}},"required":["us_cbsa_2020"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-US_CBSA_2020","type":"object"},{"description":"A hint that the data contains a US State (Census 2020) attribute.","properties":{"attribute":{"properties":{"us_state_2020":{"type":"string"}},"required":["us_state_2020"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-US_State_2020","type":"object"},{"description":"A hint that the data contains a US County (Census 2020) attribute.","properties":{"attribute":{"properties":{"us_county_2020":{"type":"string"}},"required":["us_county_2020"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-US_County_2020","type":"object"},{"description":"A hint that the data contains US Zip Code (2022) attribute.","properties":{"attribute":{"properties":{"us_zip_code_2022":{"type":"string"}},"required":["us_zip_code_2022"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-US_Zip_Code_2022","type":"object"},{"description":"A hint that the data contains a Eurostat LAU (2021) attribute. https://ec.europa.eu/eurostat/web/nuts/local-administrative-units","properties":{"attribute":{"properties":{"eu_lau_2021":{"type":"string"}},"required":["eu_lau_2021"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-EuropeanUnion_LAU_2021","type":"object"},{"description":"A hint that the data contains a Eurostat NUTS Level 1 (2021) attribute. https://ec.europa.eu/eurostat/web/gisco/geodata/statistical-units/territorial-units-statistics","properties":{"attribute":{"properties":{"eu_nuts_1_2021":{"type":"string"}},"required":["eu_nuts_1_2021"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-European_Union_NUTS_1_2021","type":"object"},{"description":"A hint that the data contains a Eurostat NUTS 2 (2021) attribute. https://ec.europa.eu/eurostat/web/gisco/geodata/statistical-units/territorial-units-statistics","properties":{"attribute":{"properties":{"eu_nuts_2_2021":{"type":"string"}},"required":["eu_nuts_2_2021"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-European_Union_NUTS_2_2021","type":"object"},{"description":"A hint that the data contains a Eurostat NUTS 3 (2021) attribute. https://ec.europa.eu/eurostat/web/gisco/geodata/statistical-units/territorial-units-statistics","properties":{"attribute":{"properties":{"eu_nuts_3_2021":{"type":"string"}},"required":["eu_nuts_3_2021"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-European_Union_NUTS_3_2021","type":"object"},{"description":"A hint that the data contains a Australian ABS postal area (2021) attribute. https://www.abs.gov.au/statistics/standards/australian-statistical-geography-standard-asgs-edition-3/jul2021-jun2026/non-abs-structures/postal-areas","properties":{"attribute":{"properties":{"aus_postal_area_2021":{"type":"string"}},"required":["aus_postal_area_2021"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-Australian_ABS_Postal_Area_2021","type":"object"},{"description":"A hint that the data contains an Administrative Level 0 (Country) attribute. https://en.wikipedia.org/wiki/List_of_administrative_divisions_by_country","properties":{"attribute":{"properties":{"admin_0":{"type":"string"}},"required":["admin_0"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-Admin0","type":"object"},{"description":"A hint that the data contains an Administrative Level 1 (Region) attribute. https://en.wikipedia.org/wiki/List_of_administrative_divisions_by_country","properties":{"attribute":{"properties":{"admin_1":{"type":"string"}},"required":["admin_1"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-Admin1","type":"object"},{"description":"A hint that the data contains a Timezone attribute.","properties":{"attribute":{"properties":{"timezone":{"type":"string"}},"required":["timezone"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-Timezone","type":"object"},{"description":"A hint that the data contains H3 attribute.","properties":{"attribute":{"properties":{"h3":{"type":"string"}},"required":["h3"],"type":"object"}},"required":["attribute"],"title":"UploadInterpretationHint-AttributeHint-H3","type":"object"}],"title":"UploadInterpretationHint"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"UploadResponse":{"properties":{"layer_group_id":{"$ref":"#/components/schemas/FeltID"},"layer_id":{"description":"The ID of the layer created by this upload. If multiple layers are included in the upload, this is the ID of the first layer in the layer group.","format":"felt_id","nullable":false,"type":"string"},"presigned_attributes":{"description":"If provided, the presigned attributes to attach to the post request","nullable":true,"type":"object"},"type":{"enum":["upload_response"],"type":"string"},"url":{"description":"If provided, the URL to post the file to","nullable":true,"type":"string"}},"title":"UploadResponse","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/upload":{"post":{"callbacks":{},"description":"Upload a file or import data from a URL to create a new layer on the map.\n\nThe `/upload` endpoint can be used for both URL and file uploads:\n\n* For URL uploads, simply making a single `POST` request to the upload endpoint is enough\n* For file uploads, the response of the initial `POST` request contain information you will use to upload the file to Amazon S3\n\nCheck our [Upload Anything](https://help.felt.com/upload-anything) docs to see what URLs are supported.\n\n#### **Uploading the file to Amazon S3**\n\nLayer files aren't uploaded directly to the Felt API. Instead, they are uploaded to an S3 bucket.\n\nThe response to this API request will include a URL and pre-signed params for you to use to upload your file. Only a single file may be uploaded — if you wish to upload several files at once, consider wrapping them in a zip file.\n\nTo upload the file, you must perform a multipart upload, and include the file contents in the `file` field.\n\n{% hint style=\"info\" %}\nWith the `felt_python` library, you can upload a file with a simple function call:\n{% endhint %}\n\n```python\nfrom felt_python import upload_file\n\nupload_file(map_id, file_name=\"features.geojson\", layer_name=\"My new layer\")\n```\n","operationId":"upload_map_layer","parameters":[{"description":"The ID of the map to upload the layer to.","in":"path","name":"map_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadLayerParams"}}},"description":"Upload layer params","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadResponse"}}},"description":"Upload layer response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Upload map layer","tags":["Layer Uploads"]}}}}
````


# Layer Library

APIs to publish layers

With these APIs, you can publish your layers to your workspace library.

## Publish map layer

> Make a layer available in the workspace library for reuse by team members.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"With these APIs, you can publish your layers to your workspace library.\n","name":"Layer Library"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"PublishLayerParams":{"additionalProperties":false,"properties":{"feltServerId":{"description":"The Felt Server to publish into. Defaults to the workspace's default Felt Server.","format":"uuid","type":"string"},"name":{"description":"The name to publish the layer under","type":"string"}},"title":"PublishLayerParams","type":"object"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layers/{layer_id}/publish":{"post":{"callbacks":{},"description":"Make a layer available in the workspace library for reuse by team members.","operationId":"publish_map_layer","parameters":[{"description":"The ID of the map where the layer is located","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer to publish","in":"path","name":"layer_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishLayerParams"}}},"description":"Publish layer params","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Layer"}}},"description":"Publish layer response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Publish map layer","tags":["Layer Library"]}}}}
```

## Publish map layer group

> Make a layer group available in the workspace library for reuse by team members.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"With these APIs, you can publish your layers to your workspace library.\n","name":"Layer Library"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"PublishLayerGroupParams":{"additionalProperties":false,"properties":{"feltServerId":{"description":"The Felt Server to publish into. Defaults to the workspace's default Felt Server.","format":"uuid","type":"string"},"name":{"description":"The name to publish the layer group under","type":"string"}},"title":"PublishLayerGroupParams","type":"object"},"LayerGroup":{"additionalProperties":false,"properties":{"caption":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"legend_visibility":{"description":"Controls how the layer group is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"nullable":false,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":false,"type":"integer"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"type":{"enum":["layer_group"],"type":"string"},"visibility_interaction":{"description":"Controls how the layer group is displayed in the legend. Defaults to `\"default\"`.","enum":["default","slider","select","multi_select"],"nullable":false,"type":"string"}},"required":["id","type","name","caption","visibility_interaction","layers"],"title":"LayerGroup","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layer_groups/{layer_group_id}/publish":{"post":{"callbacks":{},"description":"Make a layer group available in the workspace library for reuse by team members.","operationId":"publish_map_layer_group","parameters":[{"description":"The ID of the map where the layer group is located","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer group to publish","in":"path","name":"layer_group_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishLayerGroupParams"}}},"description":"Publish layer group params","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerGroup"}}},"description":"Publish layer group response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Publish map layer group","tags":["Layer Library"]}}}}
```

## List library layers

> List all layers in your workspace's library, or the felt layer library.\
> \
> You can add a layer from the library to a map by using the "Duplicate layers" API endpoint (\`POST /api/v2/duplicate\_layers\`) and the layer \`id\` provided by this endpoint.<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"With these APIs, you can publish your layers to your workspace library.\n","name":"Layer Library"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"LayerLibrary":{"properties":{"layer_groups":{"items":{"$ref":"#/components/schemas/LayerGroup"},"type":"array"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"type":{"enum":["layer_library"],"type":"string"}},"required":["type","layers","layer_groups"],"title":"LayerLibrary","type":"object"},"LayerGroup":{"additionalProperties":false,"properties":{"caption":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"},"legend_visibility":{"description":"Controls how the layer group is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"nullable":false,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":false,"type":"integer"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"type":{"enum":["layer_group"],"type":"string"},"visibility_interaction":{"description":"Controls how the layer group is displayed in the legend. Defaults to `\"default\"`.","enum":["default","slider","select","multi_select"],"nullable":false,"type":"string"}},"required":["id","type","name","caption","visibility_interaction","layers"],"title":"LayerGroup","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"Layer":{"additionalProperties":false,"properties":{"attributes":{"description":"List of the attributes on the layer","items":{"properties":{"name":{"description":"The name of the attribute","type":"string"},"type":{"description":"The type of the attribute","enum":["INTEGER","REAL","TEXT","BOOLEAN","DATE","DATETIME","GEOMETRY"],"type":"string"}},"required":["name"],"type":"object"},"nullable":true,"type":"array"},"caption":{"nullable":true,"type":"string"},"geometry_type":{"enum":["Line","Point","Polygon","Raster"],"nullable":true,"type":"string"},"hide_from_legend":{"nullable":false,"type":"boolean"},"id":{"$ref":"#/components/schemas/FeltID"},"is_spreadsheet":{"nullable":true,"type":"boolean"},"last_refreshed_at":{"description":"ISO 8601 timestamp of when the layer's data was last updated. This includes scheduled refreshes, manual refreshes, and direct feature edits.","format":"date-time","nullable":true,"type":"string"},"legend_display":{"description":"Controls how the layer is displayed in the legend.","enum":["default","name_only"],"nullable":true,"type":"string"},"legend_visibility":{"description":"Controls whether or not the layer is displayed in the legend. Defaults to \"show\".","enum":["hide","show"],"nullable":true,"type":"string"},"links":{"properties":{"components":{"type":"string"},"self":{"type":"string"}},"type":"object"},"metadata":{"$ref":"#/components/schemas/LayerMetadata"},"name":{"nullable":false,"type":"string"},"next_refresh_at":{"description":"ISO 8601 timestamp of when the next scheduled refresh will occur. Null if refresh is disabled or paused.","format":"date-time","nullable":true,"type":"string"},"ordering_key":{"description":"A sort order key used for ordering layers and layer groups in the legend","nullable":true,"type":"integer"},"paused_reason":{"description":"Why the layer's refresh is paused. Null when not paused.","enum":["consecutive_failures"],"nullable":true,"type":"string"},"progress":{"format":"float","nullable":false,"type":"number"},"refresh_period":{"enum":["15 min","30 min","hour","3 hours","6 hours","12 hours","day","week","month","disabled"],"type":"string"},"refresh_status":{"description":"Whether scheduled refresh is active, paused (due to failures), or disabled","enum":["active","paused","disabled"],"type":"string"},"status":{"enum":["uploading","processing","failed","completed"],"nullable":false,"type":"string"},"style":{"description":"The Felt Style Language style for the layer","type":"object"},"subtitle":{"deprecated":true,"description":"Deprecated: use `caption` instead.","nullable":true,"type":"string"},"tile_url":{"description":"The tile URL for this layer","nullable":true,"type":"string"},"type":{"enum":["layer"],"type":"string"}},"required":["id","type","hide_from_legend","status","caption","name","progress","geometry_type","style","refresh_period","refresh_status"],"title":"Layer","type":"object"},"LayerMetadata":{"additionalProperties":false,"properties":{"attribution_text":{"nullable":true,"type":"string"},"attribution_url":{"nullable":true,"type":"string"},"description":{"nullable":true,"type":"string"},"license":{"nullable":true,"type":"string"},"source_abbreviation":{"nullable":true,"type":"string"},"source_name":{"nullable":true,"type":"string"},"source_url":{"nullable":true,"type":"string"},"updated_at":{"format":"date","nullable":true,"type":"string"}},"title":"LayerMetadata","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/library":{"get":{"callbacks":{},"description":"List all layers in your workspace's library, or the felt layer library.\n\nYou can add a layer from the library to a map by using the \"Duplicate layers\" API endpoint (`POST /api/v2/duplicate_layers`) and the layer `id` provided by this endpoint.\n","operationId":"list_library_layers","parameters":[{"description":"","in":"query","name":"source","required":false,"schema":{"default":"workspace","description":"Defaults to listing library layers for your \"workspace\". Use \"felt\" to list layers from the Felt data library. Use \"all\" to list layers from both sources.","enum":["workspace","felt","all"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerLibrary"}}},"description":"LayerLibrary"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"List library layers","tags":["Layer Library"]}}}}
```


# Layer Exports

APIs to export layer data

With these APIs, you can export data to CSV, GeoJSON, and other formats.

## Create layer export link

> Generate a direct download link for layer data export.\
> \
> Get a link to export a layer as a GeoPackage (vector layers) or GeoTIFF (raster layers).<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"With these APIs, you can export data to CSV, GeoJSON, and other formats.\n","name":"Layer Exports"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"ExportLink":{"additionalProperties":false,"properties":{"export_link":{"nullable":false,"type":"string"}},"required":["export_link"],"title":"ExportLink","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"},"ServiceUnavailableError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"ServiceUnavailableError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layers/{layer_id}/get_export_link":{"get":{"callbacks":{},"description":"Generate a direct download link for layer data export.\n\nGet a link to export a layer as a GeoPackage (vector layers) or GeoTIFF (raster layers).\n","operationId":"create_map_layer_export_link","parameters":[{"description":"The ID of the map where the layer is located","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer to export","in":"path","name":"layer_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportLink"}}},"description":"Export link"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"}}},"description":"ServiceUnavailableError"}},"summary":"Create layer export link","tags":["Layer Exports"]}}}}
```

## Check custom export status

> Check the processing status and download availability of a custom export request.\
> \
> If the export is successful, the response will include a \`download\_url\` for accessing the exported data.<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"With these APIs, you can export data to CSV, GeoJSON, and other formats.\n","name":"Layer Exports"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"CustomExportRequestStatus":{"additionalProperties":false,"properties":{"download_url":{"nullable":true,"type":"string"},"export_id":{"nullable":false,"type":"string"},"filters":{"items":{},"nullable":false,"type":"array"},"status":{"enum":["completed","failed","in_progress"],"nullable":false,"type":"string"}},"required":["export_id","status","download_url","filters"],"title":"CustomExportRequestStatus","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layers/{layer_id}/custom_exports/{export_id}":{"get":{"callbacks":{},"description":"Check the processing status and download availability of a custom export request.\n\nIf the export is successful, the response will include a `download_url` for accessing the exported data.\n","operationId":"poll_map_layer_custom_export","parameters":[{"description":"The ID of the map where the layer is located","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer to export","in":"path","name":"layer_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the export","in":"path","name":"export_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomExportRequestStatus"}}},"description":"Custom export request status"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Check custom export status","tags":["Layer Exports"]}}}}
```

## Create custom layer export

> Start a custom export with specific format and filter options for layer data.\
> \
> Export requests are asynchronous. A successful response will return a \`poll\_endpoint\` to check the status of the export using the poll custom export endpoint.<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"With these APIs, you can export data to CSV, GeoJSON, and other formats.\n","name":"Layer Exports"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"CustomExportParams":{"additionalProperties":false,"properties":{"email_on_completion":{"description":"Send an email to the requesting user when the export completes. Defaults to `true`","type":"boolean"},"filters":{"description":"Filters for the layer in Felt Style Language filter format","items":{},"type":"array"},"output_format":{"enum":["csv","gpkg","geojson","geotiff","pmtiles","shapefile","kml","geoparquet"],"nullable":false,"type":"string"}},"required":["output_format"],"title":"CustomExportParams","type":"object"},"CustomExportResponse":{"additionalProperties":false,"properties":{"export_request_id":{"$ref":"#/components/schemas/FeltID"},"poll_endpoint":{"nullable":false,"type":"string"}},"required":["export_request_id","poll_endpoint"],"title":"CustomExportResponse","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"},"ServiceUnavailableError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"ServiceUnavailableError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layers/{layer_id}/custom_export":{"post":{"callbacks":{},"description":"Start a custom export with specific format and filter options for layer data.\n\nExport requests are asynchronous. A successful response will return a `poll_endpoint` to check the status of the export using the poll custom export endpoint.\n","operationId":"create_map_layer_custom_export","parameters":[{"description":"The ID of the map where the layer is located","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer to export","in":"path","name":"layer_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomExportParams"}}},"description":"Custom export params","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomExportResponse"}}},"description":"Custom export response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"}}},"description":"ServiceUnavailableError"}},"summary":"Create custom layer export","tags":["Layer Exports"]}}}}
```


# Layer Components

APIs to build dashboards

With these APIs, you can create and edit a layer's components.

## List layer components

> Returns all components on a layer, ordered as they appear in the app.<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"With these APIs, you can create and edit a layer's components.\n","name":"Layer Components"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"LayerComponentList":{"items":{"$ref":"#/components/schemas/LayerComponent"},"title":"LayerComponentList","type":"array"},"LayerComponent":{"anyOf":[{"additionalProperties":false,"description":"Summarizes the layer into a single statistic, such as a feature count or an attribute average.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."}},"required":["aggregate"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."}},"required":["aggregate","aggregate_by"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["statistic"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Statistic","type":"object"},{"additionalProperties":false,"description":"Shows the distribution of a numeric attribute's values across bins.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Numeric attribute to bin into the distribution.","type":"string"},"grouping":{"additionalProperties":false,"description":"How the attribute's values are binned.","properties":{"steps":{"additionalProperties":false,"properties":{"count":{"description":"Number of equal-width bins across the attribute's range.","exclusiveMinimum":true,"maximum":9007199254740991,"minimum":0,"type":"integer"},"type":{"enum":["equal-intervals"],"type":"string"}},"required":["type","count"],"type":"object"},"type":{"enum":["numeric"],"type":"string"}},"required":["type","steps"],"type":"object"}},"required":["aggregate","group_by","grouping"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Numeric attribute to bin into the distribution.","type":"string"},"grouping":{"additionalProperties":false,"description":"How the attribute's values are binned.","properties":{"steps":{"additionalProperties":false,"properties":{"count":{"description":"Number of equal-width bins across the attribute's range.","exclusiveMinimum":true,"maximum":9007199254740991,"minimum":0,"type":"integer"},"type":{"enum":["equal-intervals"],"type":"string"}},"required":["type","count"],"type":"object"},"type":{"enum":["numeric"],"type":"string"}},"required":["type","steps"],"type":"object"}},"required":["aggregate","aggregate_by","group_by","grouping"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["histogram"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Histogram","type":"object"},{"additionalProperties":false,"description":"Shows the categories of an attribute as a bar chart.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Attribute whose values become the categories.","type":"string"}},"required":["aggregate","group_by"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Attribute whose values become the categories.","type":"string"}},"required":["aggregate","aggregate_by","group_by"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["bar_chart"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Bar chart","type":"object"},{"additionalProperties":false,"description":"Charts a statistic over time buckets of a date attribute.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Date or datetime attribute which drives the time axis.","type":"string"},"grouping":{"additionalProperties":false,"description":"Time bucket per data point.","properties":{"interval":{"enum":["hour","day","week","month","year"],"type":"string"},"type":{"enum":["time-interval"],"type":"string"}},"required":["type","interval"],"type":"object"}},"required":["aggregate","group_by","grouping"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Date or datetime attribute which drives the time axis.","type":"string"},"grouping":{"additionalProperties":false,"description":"Time bucket per data point.","properties":{"interval":{"enum":["hour","day","week","month","year"],"type":"string"},"type":{"enum":["time-interval"],"type":"string"}},"required":["type","interval"],"type":"object"}},"required":["aggregate","aggregate_by","group_by","grouping"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["time_series"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Time series","type":"object"},{"additionalProperties":false,"description":"An interactive dropdown or slider for filtering the layer by an attribute's values.","properties":{"config":{"additionalProperties":false,"default":{"control_type":"dropdown"},"description":"Component configuration.","properties":{"control_type":{"default":"dropdown","description":"The type of rendered interactive component. `slider` only works with numeric attributes.","enum":["dropdown","slider"],"type":"string"},"plurality":{"description":"Whether the `dropdown` allows filtering on multiple values or just one.","enum":["single","multiple"],"type":"string"},"selection":{"description":"Selected values of `data.filter_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"sorting":{"description":"The rendered order of `dropdown` options.","enum":["Alphabetically","By feature count"],"type":"string"}},"required":["control_type"],"type":"object"},"data":{"additionalProperties":false,"properties":{"filter_by":{"description":"Attribute to filter values from.","type":"string"}},"required":["filter_by"],"type":"object"},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title. Defaults to the attribute name.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["filter"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Filter","type":"object"}],"title":"LayerComponent"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layers/{layer_id}/components":{"get":{"callbacks":{},"description":"Returns all components on a layer, ordered as they appear in the app.\n","operationId":"list_layer_components","parameters":[{"description":"The ID of the map the layer belongs to.","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer the components belong to.","in":"path","name":"layer_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerComponentList"}}},"description":"Layer components list"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"List layer components","tags":["Layer Components"]}}}}
```

## Create layer component

> Creates a component on a layer.\
> \
> {% hint style="info" %}\
> The component type is set at creation and cannot be changed later.\
> {% endhint %}<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"With these APIs, you can create and edit a layer's components.\n","name":"Layer Components"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"LayerComponentCreateParams":{"anyOf":[{"description":"Summarizes the layer into a single statistic, such as a feature count or an attribute average.","properties":{"config":{"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"type":"object"},"data":{"anyOf":[{"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."}},"required":["aggregate"],"title":"Feature count","type":"object"},{"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."}},"required":["aggregate","aggregate_by"],"title":"Aggregate","type":"object"}],"description":"How the statistic is computed."},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["statistic"],"type":"string"}},"required":["type","data"],"title":"Statistic","type":"object"},{"description":"Shows the distribution of a numeric attribute's values across bins.","properties":{"config":{"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"type":"object"},"data":{"anyOf":[{"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Numeric attribute to bin into the distribution.","type":"string"},"grouping":{"description":"A suggestion on how the attribute's values are binned. The result may be different to better fit an attribute's range.","properties":{"steps":{"properties":{"count":{"description":"Number of equal-width bins across the attribute's range.","exclusiveMinimum":true,"maximum":9007199254740991,"minimum":0,"type":"integer"},"type":{"enum":["equal-intervals"],"type":"string"}},"required":["type","count"],"type":"object"},"type":{"enum":["numeric"],"type":"string"}},"required":["type","steps"],"type":"object"}},"required":["aggregate","group_by"],"title":"Feature count","type":"object"},{"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Numeric attribute to bin into the distribution.","type":"string"},"grouping":{"description":"A suggestion on how the attribute's values are binned. The result may be different to better fit an attribute's range.","properties":{"steps":{"properties":{"count":{"description":"Number of equal-width bins across the attribute's range.","exclusiveMinimum":true,"maximum":9007199254740991,"minimum":0,"type":"integer"},"type":{"enum":["equal-intervals"],"type":"string"}},"required":["type","count"],"type":"object"},"type":{"enum":["numeric"],"type":"string"}},"required":["type","steps"],"type":"object"}},"required":["aggregate","aggregate_by","group_by"],"title":"Aggregate","type":"object"}],"description":"How to compute the histogram distribution."},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["histogram"],"type":"string"}},"required":["type","data"],"title":"Histogram","type":"object"},{"description":"Shows the categories of an attribute as a bar chart.","properties":{"config":{"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"type":"object"},"data":{"anyOf":[{"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Attribute whose values become the categories.","type":"string"}},"required":["aggregate","group_by"],"title":"Feature count","type":"object"},{"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Attribute whose values become the categories.","type":"string"}},"required":["aggregate","aggregate_by","group_by"],"title":"Aggregate","type":"object"}],"description":"How to compute the resulting categories."},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["bar_chart"],"type":"string"}},"required":["type","data"],"title":"Bar chart","type":"object"},{"description":"Charts a statistic over time buckets of a date attribute.","properties":{"config":{"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"type":"object"},"data":{"anyOf":[{"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Date or datetime attribute which drives the time axis.","type":"string"},"grouping":{"description":"Time bucket per data point.","properties":{"interval":{"enum":["hour","day","week","month","year"],"type":"string"},"type":{"enum":["time-interval"],"type":"string"}},"required":["type","interval"],"type":"object"}},"required":["aggregate","group_by"],"title":"Feature count","type":"object"},{"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Date or datetime attribute which drives the time axis.","type":"string"},"grouping":{"description":"Time bucket per data point.","properties":{"interval":{"enum":["hour","day","week","month","year"],"type":"string"},"type":{"enum":["time-interval"],"type":"string"}},"required":["type","interval"],"type":"object"}},"required":["aggregate","aggregate_by","group_by"],"title":"Aggregate","type":"object"}],"description":"How to aggregate the chosen attribute into a time series."},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["time_series"],"type":"string"}},"required":["type","data"],"title":"Time series","type":"object"},{"description":"An interactive dropdown or slider for filtering the layer by an attribute's values.","properties":{"config":{"default":{"control_type":"dropdown"},"description":"Component configuration.","properties":{"control_type":{"default":"dropdown","description":"The type of rendered interactive component. `slider` only works with numeric attributes.","enum":["dropdown","slider"],"type":"string"},"plurality":{"description":"Whether the `dropdown` allows filtering on multiple values or just one.","enum":["single","multiple"],"type":"string"},"selection":{"description":"Selected values of `data.filter_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"sorting":{"description":"The rendered order of `dropdown` options.","enum":["Alphabetically","By feature count"],"type":"string"}},"type":"object"},"data":{"description":"The attribute which the interactive component filters on.","properties":{"filter_by":{"description":"Attribute to filter values from.","type":"string"}},"required":["filter_by"],"type":"object"},"title":{"description":"Display title. Defaults to the attribute name.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["filter"],"type":"string"}},"required":["type","data"],"title":"Filter","type":"object"}],"title":"LayerComponentCreateParams"},"LayerComponent":{"anyOf":[{"additionalProperties":false,"description":"Summarizes the layer into a single statistic, such as a feature count or an attribute average.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."}},"required":["aggregate"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."}},"required":["aggregate","aggregate_by"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["statistic"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Statistic","type":"object"},{"additionalProperties":false,"description":"Shows the distribution of a numeric attribute's values across bins.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Numeric attribute to bin into the distribution.","type":"string"},"grouping":{"additionalProperties":false,"description":"How the attribute's values are binned.","properties":{"steps":{"additionalProperties":false,"properties":{"count":{"description":"Number of equal-width bins across the attribute's range.","exclusiveMinimum":true,"maximum":9007199254740991,"minimum":0,"type":"integer"},"type":{"enum":["equal-intervals"],"type":"string"}},"required":["type","count"],"type":"object"},"type":{"enum":["numeric"],"type":"string"}},"required":["type","steps"],"type":"object"}},"required":["aggregate","group_by","grouping"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Numeric attribute to bin into the distribution.","type":"string"},"grouping":{"additionalProperties":false,"description":"How the attribute's values are binned.","properties":{"steps":{"additionalProperties":false,"properties":{"count":{"description":"Number of equal-width bins across the attribute's range.","exclusiveMinimum":true,"maximum":9007199254740991,"minimum":0,"type":"integer"},"type":{"enum":["equal-intervals"],"type":"string"}},"required":["type","count"],"type":"object"},"type":{"enum":["numeric"],"type":"string"}},"required":["type","steps"],"type":"object"}},"required":["aggregate","aggregate_by","group_by","grouping"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["histogram"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Histogram","type":"object"},{"additionalProperties":false,"description":"Shows the categories of an attribute as a bar chart.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Attribute whose values become the categories.","type":"string"}},"required":["aggregate","group_by"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Attribute whose values become the categories.","type":"string"}},"required":["aggregate","aggregate_by","group_by"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["bar_chart"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Bar chart","type":"object"},{"additionalProperties":false,"description":"Charts a statistic over time buckets of a date attribute.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Date or datetime attribute which drives the time axis.","type":"string"},"grouping":{"additionalProperties":false,"description":"Time bucket per data point.","properties":{"interval":{"enum":["hour","day","week","month","year"],"type":"string"},"type":{"enum":["time-interval"],"type":"string"}},"required":["type","interval"],"type":"object"}},"required":["aggregate","group_by","grouping"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Date or datetime attribute which drives the time axis.","type":"string"},"grouping":{"additionalProperties":false,"description":"Time bucket per data point.","properties":{"interval":{"enum":["hour","day","week","month","year"],"type":"string"},"type":{"enum":["time-interval"],"type":"string"}},"required":["type","interval"],"type":"object"}},"required":["aggregate","aggregate_by","group_by","grouping"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["time_series"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Time series","type":"object"},{"additionalProperties":false,"description":"An interactive dropdown or slider for filtering the layer by an attribute's values.","properties":{"config":{"additionalProperties":false,"default":{"control_type":"dropdown"},"description":"Component configuration.","properties":{"control_type":{"default":"dropdown","description":"The type of rendered interactive component. `slider` only works with numeric attributes.","enum":["dropdown","slider"],"type":"string"},"plurality":{"description":"Whether the `dropdown` allows filtering on multiple values or just one.","enum":["single","multiple"],"type":"string"},"selection":{"description":"Selected values of `data.filter_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"sorting":{"description":"The rendered order of `dropdown` options.","enum":["Alphabetically","By feature count"],"type":"string"}},"required":["control_type"],"type":"object"},"data":{"additionalProperties":false,"properties":{"filter_by":{"description":"Attribute to filter values from.","type":"string"}},"required":["filter_by"],"type":"object"},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title. Defaults to the attribute name.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["filter"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Filter","type":"object"}],"title":"LayerComponent"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layers/{layer_id}/components":{"post":{"callbacks":{},"description":"Creates a component on a layer.\n\n{% hint style=\"info\" %}\nThe component type is set at creation and cannot be changed later.\n{% endhint %}\n","operationId":"create_layer_component","parameters":[{"description":"The ID of the map the layer belongs to.","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer the component belongs to.","in":"path","name":"layer_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerComponentCreateParams"}}},"description":"Layer component create params","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerComponent"}}},"description":"Layer component"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Create layer component","tags":["Layer Components"]}}}}
```

## Get layer component

> Returns a single layer component. Includes all fields.<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"With these APIs, you can create and edit a layer's components.\n","name":"Layer Components"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"LayerComponent":{"anyOf":[{"additionalProperties":false,"description":"Summarizes the layer into a single statistic, such as a feature count or an attribute average.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."}},"required":["aggregate"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."}},"required":["aggregate","aggregate_by"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["statistic"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Statistic","type":"object"},{"additionalProperties":false,"description":"Shows the distribution of a numeric attribute's values across bins.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Numeric attribute to bin into the distribution.","type":"string"},"grouping":{"additionalProperties":false,"description":"How the attribute's values are binned.","properties":{"steps":{"additionalProperties":false,"properties":{"count":{"description":"Number of equal-width bins across the attribute's range.","exclusiveMinimum":true,"maximum":9007199254740991,"minimum":0,"type":"integer"},"type":{"enum":["equal-intervals"],"type":"string"}},"required":["type","count"],"type":"object"},"type":{"enum":["numeric"],"type":"string"}},"required":["type","steps"],"type":"object"}},"required":["aggregate","group_by","grouping"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Numeric attribute to bin into the distribution.","type":"string"},"grouping":{"additionalProperties":false,"description":"How the attribute's values are binned.","properties":{"steps":{"additionalProperties":false,"properties":{"count":{"description":"Number of equal-width bins across the attribute's range.","exclusiveMinimum":true,"maximum":9007199254740991,"minimum":0,"type":"integer"},"type":{"enum":["equal-intervals"],"type":"string"}},"required":["type","count"],"type":"object"},"type":{"enum":["numeric"],"type":"string"}},"required":["type","steps"],"type":"object"}},"required":["aggregate","aggregate_by","group_by","grouping"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["histogram"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Histogram","type":"object"},{"additionalProperties":false,"description":"Shows the categories of an attribute as a bar chart.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Attribute whose values become the categories.","type":"string"}},"required":["aggregate","group_by"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Attribute whose values become the categories.","type":"string"}},"required":["aggregate","aggregate_by","group_by"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["bar_chart"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Bar chart","type":"object"},{"additionalProperties":false,"description":"Charts a statistic over time buckets of a date attribute.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Date or datetime attribute which drives the time axis.","type":"string"},"grouping":{"additionalProperties":false,"description":"Time bucket per data point.","properties":{"interval":{"enum":["hour","day","week","month","year"],"type":"string"},"type":{"enum":["time-interval"],"type":"string"}},"required":["type","interval"],"type":"object"}},"required":["aggregate","group_by","grouping"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Date or datetime attribute which drives the time axis.","type":"string"},"grouping":{"additionalProperties":false,"description":"Time bucket per data point.","properties":{"interval":{"enum":["hour","day","week","month","year"],"type":"string"},"type":{"enum":["time-interval"],"type":"string"}},"required":["type","interval"],"type":"object"}},"required":["aggregate","aggregate_by","group_by","grouping"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["time_series"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Time series","type":"object"},{"additionalProperties":false,"description":"An interactive dropdown or slider for filtering the layer by an attribute's values.","properties":{"config":{"additionalProperties":false,"default":{"control_type":"dropdown"},"description":"Component configuration.","properties":{"control_type":{"default":"dropdown","description":"The type of rendered interactive component. `slider` only works with numeric attributes.","enum":["dropdown","slider"],"type":"string"},"plurality":{"description":"Whether the `dropdown` allows filtering on multiple values or just one.","enum":["single","multiple"],"type":"string"},"selection":{"description":"Selected values of `data.filter_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"sorting":{"description":"The rendered order of `dropdown` options.","enum":["Alphabetically","By feature count"],"type":"string"}},"required":["control_type"],"type":"object"},"data":{"additionalProperties":false,"properties":{"filter_by":{"description":"Attribute to filter values from.","type":"string"}},"required":["filter_by"],"type":"object"},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title. Defaults to the attribute name.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["filter"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Filter","type":"object"}],"title":"LayerComponent"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layers/{layer_id}/components/{component_id}":{"get":{"callbacks":{},"description":"Returns a single layer component. Includes all fields.\n","operationId":"show_layer_component","parameters":[{"description":"The ID of the map the layer belongs to.","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer the component belongs to.","in":"path","name":"layer_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer component.","in":"path","name":"component_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerComponent"}}},"description":"Layer component"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Get layer component","tags":["Layer Components"]}}}}
```

## Update layer component

> Partially update the provided fields of a component.\
> \
> {% hint style="info" %}\
> Omitted fields keep their current value. A component's type is immutable and is not part of the update body.\
> {% endhint %}<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"With these APIs, you can create and edit a layer's components.\n","name":"Layer Components"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"LayerComponentUpdateParams":{"anyOf":[{"description":"Summarizes the layer into a single statistic, such as a feature count or an attribute average.","properties":{"config":{"description":"Component configuration. Provided keys are updated; omitted keys keep their current value.","properties":{"reactive":{"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"viewport_mode":{"description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"type":"object"},"data":{"anyOf":[{"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."}},"required":["aggregate"],"title":"Feature count","type":"object"},{"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."}},"required":["aggregate","aggregate_by"],"title":"Aggregate","type":"object"}],"description":"Change how the component computes its result. `data` replaces as a unit when provided. Partial updates are not supported."},"title":{"description":"Display title.","type":"string"}},"title":"Statistic","type":"object"},{"description":"Shows the distribution of a numeric attribute's values across bins.","properties":{"config":{"description":"Component configuration. Provided keys are updated; omitted keys keep their current value.","properties":{"reactive":{"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block). Pass `null` to clear the selection."},"viewport_mode":{"description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"type":"object"},"data":{"anyOf":[{"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Numeric attribute to bin into the distribution.","type":"string"},"grouping":{"description":"A suggestion on how the attribute's values are binned. The result may be different to better fit an attribute's range.","properties":{"steps":{"properties":{"count":{"description":"Number of equal-width bins across the attribute's range.","exclusiveMinimum":true,"maximum":9007199254740991,"minimum":0,"type":"integer"},"type":{"enum":["equal-intervals"],"type":"string"}},"required":["type","count"],"type":"object"},"type":{"enum":["numeric"],"type":"string"}},"required":["type","steps"],"type":"object"}},"required":["aggregate","group_by"],"title":"Feature count","type":"object"},{"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Numeric attribute to bin into the distribution.","type":"string"},"grouping":{"description":"A suggestion on how the attribute's values are binned. The result may be different to better fit an attribute's range.","properties":{"steps":{"properties":{"count":{"description":"Number of equal-width bins across the attribute's range.","exclusiveMinimum":true,"maximum":9007199254740991,"minimum":0,"type":"integer"},"type":{"enum":["equal-intervals"],"type":"string"}},"required":["type","count"],"type":"object"},"type":{"enum":["numeric"],"type":"string"}},"required":["type","steps"],"type":"object"}},"required":["aggregate","aggregate_by","group_by"],"title":"Aggregate","type":"object"}],"description":"Change how the component computes its result. `data` replaces as a unit when provided. Partial updates are not supported."},"title":{"description":"Display title.","type":"string"}},"title":"Histogram","type":"object"},{"description":"Shows the categories of an attribute as a bar chart.","properties":{"config":{"description":"Component configuration. Provided keys are updated; omitted keys keep their current value.","properties":{"reactive":{"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block). Pass `null` to clear the selection."},"viewport_mode":{"description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"type":"object"},"data":{"anyOf":[{"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Attribute whose values become the categories.","type":"string"}},"required":["aggregate","group_by"],"title":"Feature count","type":"object"},{"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Attribute whose values become the categories.","type":"string"}},"required":["aggregate","aggregate_by","group_by"],"title":"Aggregate","type":"object"}],"description":"Change how the component computes its result. `data` replaces as a unit when provided. Partial updates are not supported."},"title":{"description":"Display title.","type":"string"}},"title":"Bar chart","type":"object"},{"description":"Charts a statistic over time buckets of a date attribute.","properties":{"config":{"description":"Component configuration. Provided keys are updated; omitted keys keep their current value.","properties":{"reactive":{"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block). Pass `null` to clear the selection."},"viewport_mode":{"description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"type":"object"},"data":{"anyOf":[{"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Date or datetime attribute which drives the time axis.","type":"string"},"grouping":{"description":"Time bucket per data point.","properties":{"interval":{"enum":["hour","day","week","month","year"],"type":"string"},"type":{"enum":["time-interval"],"type":"string"}},"required":["type","interval"],"type":"object"}},"required":["aggregate","group_by"],"title":"Feature count","type":"object"},{"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Date or datetime attribute which drives the time axis.","type":"string"},"grouping":{"description":"Time bucket per data point.","properties":{"interval":{"enum":["hour","day","week","month","year"],"type":"string"},"type":{"enum":["time-interval"],"type":"string"}},"required":["type","interval"],"type":"object"}},"required":["aggregate","aggregate_by","group_by"],"title":"Aggregate","type":"object"}],"description":"Change how the component computes its result. `data` replaces as a unit when provided. Partial updates are not supported."},"title":{"description":"Display title.","type":"string"}},"title":"Time series","type":"object"},{"description":"An interactive dropdown or slider for filtering the layer by an attribute's values.","properties":{"config":{"description":"Component configuration. Provided keys are updated; omitted keys keep their current value.","properties":{"control_type":{"description":"The type of rendered interactive component. `slider` only works with numeric attributes.","enum":["dropdown","slider"],"type":"string"},"plurality":{"description":"Whether the `dropdown` allows filtering on multiple values or just one.","enum":["single","multiple"],"type":"string"},"selection":{"description":"Selected values of `data.filter_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block). Pass `null` to clear the selection."},"sorting":{"description":"The rendered order of `dropdown` options.","enum":["Alphabetically","By feature count"],"type":"string"}},"type":"object"},"data":{"description":"Change how the component computes its result. `data` replaces as a unit when provided. Partial updates are not supported.","properties":{"filter_by":{"description":"Attribute to filter values from.","type":"string"}},"required":["filter_by"],"type":"object"},"title":{"description":"Display title.","type":"string"}},"title":"Filter","type":"object"}],"title":"LayerComponentUpdateParams"},"LayerComponent":{"anyOf":[{"additionalProperties":false,"description":"Summarizes the layer into a single statistic, such as a feature count or an attribute average.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."}},"required":["aggregate"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."}},"required":["aggregate","aggregate_by"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["statistic"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Statistic","type":"object"},{"additionalProperties":false,"description":"Shows the distribution of a numeric attribute's values across bins.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Numeric attribute to bin into the distribution.","type":"string"},"grouping":{"additionalProperties":false,"description":"How the attribute's values are binned.","properties":{"steps":{"additionalProperties":false,"properties":{"count":{"description":"Number of equal-width bins across the attribute's range.","exclusiveMinimum":true,"maximum":9007199254740991,"minimum":0,"type":"integer"},"type":{"enum":["equal-intervals"],"type":"string"}},"required":["type","count"],"type":"object"},"type":{"enum":["numeric"],"type":"string"}},"required":["type","steps"],"type":"object"}},"required":["aggregate","group_by","grouping"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Numeric attribute to bin into the distribution.","type":"string"},"grouping":{"additionalProperties":false,"description":"How the attribute's values are binned.","properties":{"steps":{"additionalProperties":false,"properties":{"count":{"description":"Number of equal-width bins across the attribute's range.","exclusiveMinimum":true,"maximum":9007199254740991,"minimum":0,"type":"integer"},"type":{"enum":["equal-intervals"],"type":"string"}},"required":["type","count"],"type":"object"},"type":{"enum":["numeric"],"type":"string"}},"required":["type","steps"],"type":"object"}},"required":["aggregate","aggregate_by","group_by","grouping"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["histogram"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Histogram","type":"object"},{"additionalProperties":false,"description":"Shows the categories of an attribute as a bar chart.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Attribute whose values become the categories.","type":"string"}},"required":["aggregate","group_by"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Attribute whose values become the categories.","type":"string"}},"required":["aggregate","aggregate_by","group_by"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["bar_chart"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Bar chart","type":"object"},{"additionalProperties":false,"description":"Charts a statistic over time buckets of a date attribute.","properties":{"config":{"additionalProperties":false,"default":{"reactive":true,"viewport_mode":"global"},"description":"Component configuration.","properties":{"reactive":{"default":true,"description":"Whether this component reacts to other components' selections, filtering its result to match.","type":"boolean"},"selection":{"description":"Selected values of `data.group_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"viewport_mode":{"default":"global","description":"Compute over the whole layer (`global`) or only the features in the current viewport (`viewport`).","enum":["global","viewport"],"type":"string"}},"required":["reactive","viewport_mode"],"type":"object"},"data":{"anyOf":[{"additionalProperties":false,"properties":{"aggregate":{"description":"Count features.","enum":["count"],"type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Date or datetime attribute which drives the time axis.","type":"string"},"grouping":{"additionalProperties":false,"description":"Time bucket per data point.","properties":{"interval":{"enum":["hour","day","week","month","year"],"type":"string"},"type":{"enum":["time-interval"],"type":"string"}},"required":["type","interval"],"type":"object"}},"required":["aggregate","group_by","grouping"],"title":"Feature count","type":"object"},{"additionalProperties":false,"properties":{"aggregate":{"description":"The statistic to compute over `aggregate_by`: sum, average, min, max, median, or count of distinct values.","enum":["sum","avg","min","max","median","count_distinct"],"type":"string"},"aggregate_by":{"description":"Attribute to aggregate.","type":"string"},"format":{"description":"numbrojs format object. See the [numbrojs format documentation](https://numbrojs.com/format.html)."},"group_by":{"description":"Date or datetime attribute which drives the time axis.","type":"string"},"grouping":{"additionalProperties":false,"description":"Time bucket per data point.","properties":{"interval":{"enum":["hour","day","week","month","year"],"type":"string"},"type":{"enum":["time-interval"],"type":"string"}},"required":["type","interval"],"type":"object"}},"required":["aggregate","aggregate_by","group_by","grouping"],"title":"Aggregate","type":"object"}]},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["time_series"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Time series","type":"object"},{"additionalProperties":false,"description":"An interactive dropdown or slider for filtering the layer by an attribute's values.","properties":{"config":{"additionalProperties":false,"default":{"control_type":"dropdown"},"description":"Component configuration.","properties":{"control_type":{"default":"dropdown","description":"The type of rendered interactive component. `slider` only works with numeric attributes.","enum":["dropdown","slider"],"type":"string"},"plurality":{"description":"Whether the `dropdown` allows filtering on multiple values or just one.","enum":["single","multiple"],"type":"string"},"selection":{"description":"Selected values of `data.filter_by`. The visualized map layer and other components are filtered to match. Uses the same format as the [FSL filters block](https://developers.felt.com/felt-style-language/style-definition-blocks/the-filters-block)."},"sorting":{"description":"The rendered order of `dropdown` options.","enum":["Alphabetically","By feature count"],"type":"string"}},"required":["control_type"],"type":"object"},"data":{"additionalProperties":false,"properties":{"filter_by":{"description":"Attribute to filter values from.","type":"string"}},"required":["filter_by"],"type":"object"},"id":{"description":"Server-assigned component id.","type":"string"},"layer_id":{"description":"The layer this component is bound to.","type":"string"},"title":{"description":"Display title. Defaults to the attribute name.","type":"string"},"type":{"description":"The component type. Immutable after creation.","enum":["filter"],"type":"string"}},"required":["type","data","config","id","layer_id"],"title":"Filter","type":"object"}],"title":"LayerComponent"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layers/{layer_id}/components/{component_id}":{"post":{"callbacks":{},"description":"Partially update the provided fields of a component.\n\n{% hint style=\"info\" %}\nOmitted fields keep their current value. A component's type is immutable and is not part of the update body.\n{% endhint %}\n","operationId":"update_layer_component","parameters":[{"description":"The ID of the map the layer belongs to.","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer the component belongs to.","in":"path","name":"layer_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer component.","in":"path","name":"component_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerComponentUpdateParams"}}},"description":"Layer component update params","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerComponent"}}},"description":"Layer component"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Update layer component","tags":["Layer Components"]}}}}
```

## Delete layer component

> Deletes a component from the layer.\
> \
> {% hint style="warning" %}\
> This cannot be undone.\
> {% endhint %}<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"With these APIs, you can create and edit a layer's components.\n","name":"Layer Components"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/layers/{layer_id}/components/{component_id}":{"delete":{"callbacks":{},"description":"Deletes a component from the layer.\n\n{% hint style=\"warning\" %}\nThis cannot be undone.\n{% endhint %}\n","operationId":"delete_layer_component","parameters":[{"description":"The ID of the map the layer belongs to.","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer the component belongs to.","in":"path","name":"layer_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the layer component.","in":"path","name":"component_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Delete layer component","tags":["Layer Components"]}}}}
```


# Elements

APIs for drawing spatially

Elements enable you to annotate maps with custom shapes, text, and markers.

With these APIs, you can create, update, and delete map elements.

## Delete map element

> Permanently delete an element from a map.\
> \
> {% hint style="warning" %}\
> This action cannot be undone. The element will be permanently removed from the map.\
> {% endhint %}<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Elements enable you to annotate maps with custom shapes, text, and markers.\n\nWith these APIs, you can create, update, and delete map elements.\n","name":"Elements"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/elements/{element_id}":{"delete":{"callbacks":{},"description":"Permanently delete an element from a map.\n\n{% hint style=\"warning\" %}\nThis action cannot be undone. The element will be permanently removed from the map.\n{% endhint %}\n","operationId":"delete_map_element","parameters":[{"description":"The ID of the map to delete the element from.","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the element to delete.","in":"path","name":"element_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Delete map element","tags":["Elements"]}}}}
```

## List map elements

> Returns a GeoJSON \`FeatureCollection\` containing all the elements in a map that are not in an element group.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Elements enable you to annotate maps with custom shapes, text, and markers.\n\nWith these APIs, you can create, update, and delete map elements.\n","name":"Elements"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"GeoJSON":{"properties":{"features":{"items":{"properties":{"geometry":{"properties":{"felt:id":{"$ref":"#/components/schemas/FeltID"},"felt:parentId":{"format":"felt_id","nullable":true,"type":"string"}},"type":"object"},"properties":{"type":"object"},"type":{"enum":["Feature"],"type":"string"}},"type":"object"},"type":"array"},"type":{"enum":["FeatureCollection"],"type":"string"}},"required":["type","features"],"title":"GeoJSON","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/elements":{"get":{"callbacks":{},"description":"Returns a GeoJSON `FeatureCollection` containing all the elements in a map that are not in an element group.","operationId":"list_map_elements","parameters":[{"description":"The ID of the map to list elements from.","in":"path","name":"map_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeoJSON"}}},"description":"GeoJSON"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"List map elements","tags":["Elements"]}}}}
```

## Create or update map elements

> Create new elements or update existing ones on a map using GeoJSON data.\
> Each element is represented by a feature in the \`POST\`'ed GeoJSON Feature Collection.\
> For each feature, including an existing element \`id\` will result in the element being updated on the map. If no element \`id\` is provided (or a non-existent one), a new element will be created.\
> \
> {% hint style="info" %}\
> The maximum payload size for any \`POST\` to the Felt API is 1MB. Additionally, complex element geometry may be automatically simplified. If you require large, complex geometries, consider uploading your data as a Data Layer.\&#x20;\
> {% endhint %}<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Elements enable you to annotate maps with custom shapes, text, and markers.\n\nWith these APIs, you can create, update, and delete map elements.\n","name":"Elements"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"GeoJSON":{"properties":{"features":{"items":{"properties":{"geometry":{"properties":{"felt:id":{"$ref":"#/components/schemas/FeltID"},"felt:parentId":{"format":"felt_id","nullable":true,"type":"string"}},"type":"object"},"properties":{"type":"object"},"type":{"enum":["Feature"],"type":"string"}},"type":"object"},"type":"array"},"type":{"enum":["FeatureCollection"],"type":"string"}},"required":["type","features"],"title":"GeoJSON","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/elements":{"post":{"callbacks":{},"description":"Create new elements or update existing ones on a map using GeoJSON data.\nEach element is represented by a feature in the `POST`'ed GeoJSON Feature Collection.\nFor each feature, including an existing element `id` will result in the element being updated on the map. If no element `id` is provided (or a non-existent one), a new element will be created.\n\n{% hint style=\"info\" %}\nThe maximum payload size for any `POST` to the Felt API is 1MB. Additionally, complex element geometry may be automatically simplified. If you require large, complex geometries, consider uploading your data as a Data Layer.&#x20;\n{% endhint %}\n","operationId":"upsert_map_elements","parameters":[{"description":"The ID of the map to create the elements in","in":"path","name":"map_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeoJSON"}}},"description":"Upsert element parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeoJSON"}}},"description":"GeoJSON"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Create or update map elements","tags":["Elements"]}}}}
```

## Get map element group

> Retrieve all elements from a specific group as GeoJSON.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Elements enable you to annotate maps with custom shapes, text, and markers.\n\nWith these APIs, you can create, update, and delete map elements.\n","name":"Elements"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"GeoJSON":{"properties":{"features":{"items":{"properties":{"geometry":{"properties":{"felt:id":{"$ref":"#/components/schemas/FeltID"},"felt:parentId":{"format":"felt_id","nullable":true,"type":"string"}},"type":"object"},"properties":{"type":"object"},"type":{"enum":["Feature"],"type":"string"}},"type":"object"},"type":"array"},"type":{"enum":["FeatureCollection"],"type":"string"}},"required":["type","features"],"title":"GeoJSON","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/element_groups/{group_id}":{"get":{"callbacks":{},"description":"Retrieve all elements from a specific group as GeoJSON.","operationId":"show_map_element_group","parameters":[{"description":"The ID of the map.","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the element group.","in":"path","name":"group_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeoJSON"}}},"description":"GeoJSON"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Get map element group","tags":["Elements"]}}}}
```

## List map element groups

> Returns a list of GeoJSON \`FeatureCollection\`s, one for each element group in the map.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Elements enable you to annotate maps with custom shapes, text, and markers.\n\nWith these APIs, you can create, update, and delete map elements.\n","name":"Elements"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"ElementGroupList":{"items":{"$ref":"#/components/schemas/ElementGroup"},"title":"ElementGroupList","type":"array"},"ElementGroup":{"additionalProperties":false,"properties":{"color":{"description":"The color of the element group symbol.","nullable":false,"type":"string"},"elements":{"$ref":"#/components/schemas/GeoJSON"},"id":{"description":"The ID of the element group.","type":"string"},"name":{"description":"The name of the element group.","nullable":true,"type":"string"},"symbol":{"description":"The symbol used to represent the element group.","nullable":true,"type":"string"}},"required":["id","color","name","symbol","elements"],"title":"ElementGroup","type":"object"},"GeoJSON":{"properties":{"features":{"items":{"properties":{"geometry":{"properties":{"felt:id":{"$ref":"#/components/schemas/FeltID"},"felt:parentId":{"format":"felt_id","nullable":true,"type":"string"}},"type":"object"},"properties":{"type":"object"},"type":{"enum":["Feature"],"type":"string"}},"type":"object"},"type":"array"},"type":{"enum":["FeatureCollection"],"type":"string"}},"required":["type","features"],"title":"GeoJSON","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/element_groups":{"get":{"callbacks":{},"description":"Returns a list of GeoJSON `FeatureCollection`s, one for each element group in the map.","operationId":"list_map_element_groups","parameters":[{"description":"The ID of the map to list groups from.","in":"path","name":"map_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ElementGroupList"}}},"description":"ElementGroupList"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"List map element groups","tags":["Elements"]}}}}
```

## Create or update map element groups

> Create new element groups or update existing ones.\
> \
> For each Element Group, including an existing Element Group \`id\` will result in the Element Group being updated. If no \`id\` is provided, a new Element Group will be created.\
> \
> \
> {% hint style="info" %}\
> The maximum payload size for any \`POST\` to the Felt API is 1MB. Additionally, complex element geometry may be automatically simplified. If you require large, complex geometries, consider uploading your data as a Data Layer.\&#x20;\
> {% endhint %}<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Elements enable you to annotate maps with custom shapes, text, and markers.\n\nWith these APIs, you can create, update, and delete map elements.\n","name":"Elements"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"ElementGroupParamsList":{"items":{"$ref":"#/components/schemas/ElementGroupParams"},"title":"ElementGroupParamsList","type":"array"},"ElementGroupParams":{"additionalProperties":false,"properties":{"color":{"default":"#C93535","type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"name":{"type":"string"},"symbol":{"default":"dot","type":"string"}},"required":["name"],"title":"ElementGroupParams","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"ElementGroupList":{"items":{"$ref":"#/components/schemas/ElementGroup"},"title":"ElementGroupList","type":"array"},"ElementGroup":{"additionalProperties":false,"properties":{"color":{"description":"The color of the element group symbol.","nullable":false,"type":"string"},"elements":{"$ref":"#/components/schemas/GeoJSON"},"id":{"description":"The ID of the element group.","type":"string"},"name":{"description":"The name of the element group.","nullable":true,"type":"string"},"symbol":{"description":"The symbol used to represent the element group.","nullable":true,"type":"string"}},"required":["id","color","name","symbol","elements"],"title":"ElementGroup","type":"object"},"GeoJSON":{"properties":{"features":{"items":{"properties":{"geometry":{"properties":{"felt:id":{"$ref":"#/components/schemas/FeltID"},"felt:parentId":{"format":"felt_id","nullable":true,"type":"string"}},"type":"object"},"properties":{"type":"object"},"type":{"enum":["Feature"],"type":"string"}},"type":"object"},"type":"array"},"type":{"enum":["FeatureCollection"],"type":"string"}},"required":["type","features"],"title":"GeoJSON","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/element_groups":{"post":{"callbacks":{},"description":"Create new element groups or update existing ones.\n\nFor each Element Group, including an existing Element Group `id` will result in the Element Group being updated. If no `id` is provided, a new Element Group will be created.\n\n\n{% hint style=\"info\" %}\nThe maximum payload size for any `POST` to the Felt API is 1MB. Additionally, complex element geometry may be automatically simplified. If you require large, complex geometries, consider uploading your data as a Data Layer.&#x20;\n{% endhint %}\n","operationId":"upsert_map_element_groups","parameters":[{"description":"The ID of the map to create the group in","in":"path","name":"map_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ElementGroupParamsList"}}},"description":"Upsert element groups parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ElementGroupList"}}},"description":"Element group list"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Create or update map element groups","tags":["Elements"]}}}}
```


# Users

APIs for user information

Users represent the people in your workspace.

With these APIs, you can retrieve user profile information.

## Get current user

> Retrieve profile information and settings for the authenticated user.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Users represent the people in your workspace.\n\nWith these APIs, you can retrieve user profile information.\n","name":"Users"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"User":{"properties":{"email":{"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"name":{"type":"string"}},"title":"User","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/user":{"get":{"callbacks":{},"description":"Retrieve profile information and settings for the authenticated user.","operationId":"show_current_user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}},"description":"User"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Get current user","tags":["Users"]}}}}
```


# Comments

APIs for programatic collaboration

Comments bring conversations to mapping.

With these APIs, you can export, resolve, and delete map comments and collaboration threads.

## Export map comments

> Export all comments and replies from a map in \`CSV\`, \`JSON\`, or \`GeoJSON\` format.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Comments bring conversations to mapping.\n\nWith these APIs, you can export, resolve, and delete map comments and collaboration threads.\n","name":"Comments"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"CommentExport":{"items":{"description":"Comment Thread"},"title":"CommentExport","type":"array"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/comments/export":{"get":{"callbacks":{},"description":"Export all comments and replies from a map in `CSV`, `JSON`, or `GeoJSON` format.","operationId":"export_map_comments","parameters":[{"description":"The ID of the map to export comments from.","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The format to export the comments in: 'csv', 'json' (default), or 'geojson'","in":"query","name":"format","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentExport"}}},"description":"Comment export response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Export map comments","tags":["Comments"]}}}}
```

## Resolve map comment

> Mark a comment thread as resolved.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Comments bring conversations to mapping.\n\nWith these APIs, you can export, resolve, and delete map comments and collaboration threads.\n","name":"Comments"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"CommentResolved":{"properties":{"comment_id":{"$ref":"#/components/schemas/FeltID"}},"title":"CommentResolved","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/comments/{comment_id}/resolve":{"post":{"callbacks":{},"description":"Mark a comment thread as resolved.","operationId":"resolve_map_comment","parameters":[{"description":"The ID of the map that contains the comment.","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the comment to resolve.","in":"path","name":"comment_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentResolved"}}},"description":"Comment resolved response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Resolve map comment","tags":["Comments"]}}}}
```

## Delete map comment

> Permanently delete a comment or reply from the map.\
> \
> {% hint style="warning" %}\
> This action cannot be undone. The comment or reply will be permanently removed from the map.\
> {% endhint %}<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Comments bring conversations to mapping.\n\nWith these APIs, you can export, resolve, and delete map comments and collaboration threads.\n","name":"Comments"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/comments/{comment_id}":{"delete":{"callbacks":{},"description":"Permanently delete a comment or reply from the map.\n\n{% hint style=\"warning\" %}\nThis action cannot be undone. The comment or reply will be permanently removed from the map.\n{% endhint %}\n","operationId":"delete_map_comment","parameters":[{"description":"The ID of the map that contains the comment.","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the comment to delete.","in":"path","name":"comment_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Delete map comment","tags":["Comments"]}}}}
```


# Embed Tokens

APIs to share maps securely

Embed tokens enable safely sharing your private maps.

With these APIs, you can generate secure tokens for embedding maps.

## Create an Embed Token

> Creates a token, valid for 12 hours, for authenticating a visitor to view a private embedded map view without being logged into Felt. You must provide a \`user\_email\` to associate the token with the end user that will be viewing the map.\
> Each end user should be a member of your Felt workspace with a viewer, editor, or admin role assigned.\
> \
> Mint a fresh token for each page load. The 12 hours cover a full working session in one open tab — reconnects included — and when they run out the embed asks the visitor to refresh, at which point your page should mint again.\
> \### Usage\
> \* Generate a token by making a call to this API from your server\
> \* Securely pass the token to your frontend client\
> \* Include the token as a query parameter on the Embed URL in an iframe\
> \
> \`\`\`html\
> \<iframe src="[https://felt.com/embed/map/{mapId}?token={token}">\\](https://felt.com/embed/map/{mapId}?token={token}">\\)</iframe>\
> \`\`\`\
> \
> \#### Enabling Layer Export\
> \
> You can allow EmbedToken based page views to export layer data.\
> \
> \* Turn on "Viewer permissions: Export data" in Map settings<br>

````json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Embed tokens enable safely sharing your private maps.\n\nWith these APIs, you can generate secure tokens for embedding maps.\n","name":"Embed Tokens"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"EmbedToken":{"properties":{"expires_at":{"format":"date_time","type":"string"},"token":{"type":"string"}},"title":"EmbedToken","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/maps/{map_id}/embed_token":{"post":{"callbacks":{},"description":"Creates a token, valid for 12 hours, for authenticating a visitor to view a private embedded map view without being logged into Felt. You must provide a `user_email` to associate the token with the end user that will be viewing the map.\nEach end user should be a member of your Felt workspace with a viewer, editor, or admin role assigned.\n\nMint a fresh token for each page load. The 12 hours cover a full working session in one open tab — reconnects included — and when they run out the embed asks the visitor to refresh, at which point your page should mint again.\n### Usage\n* Generate a token by making a call to this API from your server\n* Securely pass the token to your frontend client\n* Include the token as a query parameter on the Embed URL in an iframe\n\n```html\n<iframe src=\"https://felt.com/embed/map/{mapId}?token={token}\"></iframe>\n```\n\n#### Enabling Layer Export\n\nYou can allow EmbedToken based page views to export layer data.\n\n* Turn on \"Viewer permissions: Export data\" in Map settings\n","operationId":"create_map_embed_token","parameters":[{"description":"","in":"path","name":"map_id","required":true,"schema":{"type":"string"}},{"description":"Each token must be associated with the email address of the user who will use it.","in":"query","name":"user_email","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedToken"}}},"description":"EmbedToken"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Create an Embed Token","tags":["Embed Tokens"]}}}}
````


# Sources

APIs to connect your data

Sources connect your databases to Felt.

With these APIs, you can configure data source connections, credentials, and sync settings to create live maps.

## Update source

> Update data source connection settings, access permissions, or configuration details.\
> \
> Connecting the Source and inspecting its datasets will happen asynchronously after the API response is returned. To determine when the inspection process has completed, poll the Show Source endpoint and check for \`sync\_status: completed\`.<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Sources connect your databases to Felt.\n\nWith these APIs, you can configure data source connections, credentials, and sync settings to create live maps.\n","name":"Sources"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"SourceUpdateParams":{"additionalProperties":false,"properties":{"connection":{"$ref":"#/components/schemas/SourceUpdateConnectionParams"},"name":{"type":"string"},"permissions":{"$ref":"#/components/schemas/SourcePermissions"}},"title":"SourceUpdateParams","type":"object"},"SourceUpdateConnectionParams":{"oneOf":[{"additionalProperties":false,"properties":{"blob_storage_url":{"description":"ABS blob storage URL","type":"string"},"type":{"enum":["abs_bucket"],"type":"string"}},"required":["type"],"title":"SourceUpdateConnectionParams-ABS","type":"object"},{"additionalProperties":false,"properties":{"base64_encoded_service_account":{"description":"BigQuery credentials - Base 64 encoded Service account JSON","nullable":true,"type":"string"},"dataset":{"description":"BigQuery dataset","nullable":true,"type":"string"},"project":{"description":"BigQuery project","type":"string"},"type":{"enum":["bigquery"],"type":"string"}},"required":["type"],"title":"SourceUpdateConnectionParams-GoogleBigQuery","type":"object"},{"additionalProperties":false,"properties":{"catalog":{"description":"Databricks catalog","nullable":true,"type":"string"},"http_path":{"description":"Databricks server HTTP path","type":"string"},"schema":{"description":"Databricks schema","nullable":true,"type":"string"},"server_hostname":{"description":"Databricks server hostname","type":"string"},"type":{"enum":["databricks"],"type":"string"}},"required":["type"],"title":"SourceUpdateConnectionParams-Databricks","type":"object"},{"additionalProperties":false,"properties":{"token":{"description":"ESRI server token","nullable":true,"type":"string"},"type":{"enum":["feature_server"],"type":"string"},"url":{"description":"ESRI FeatureServer, MapServer, or ImageServer URL","type":"string"}},"required":["type"],"title":"SourceUpdateConnectionParams-ESRIFeatureServer","type":"object"},{"additionalProperties":false,"properties":{"gs_uri":{"description":"GCS URI","type":"string"},"type":{"enum":["gcs_bucket"],"type":"string"}},"required":["type"],"title":"SourceUpdateConnectionParams-GCS","type":"object"},{"additionalProperties":false,"properties":{"database":{"description":"MSSQL database name","type":"string"},"host":{"description":"MSSQL host","type":"string"},"password":{"description":"MSSQL password","type":"string"},"port":{"description":"MSSQL port","nullable":true,"type":"integer"},"type":{"enum":["mssql"],"type":"string"},"user":{"description":"MSSQL user name","type":"string"}},"required":["type"],"title":"SourceUpdateConnectionParams-MicrosoftSQL","type":"object"},{"additionalProperties":false,"description":"Postgres / PostGIS","properties":{"database":{"description":"Postgres database name","type":"string"},"host":{"description":"Postgres host","type":"string"},"password":{"description":"Postgres password","type":"string"},"port":{"description":"Postgres port","nullable":true,"type":"integer"},"schema":{"description":"Postgres schema","type":"string"},"type":{"enum":["postgresql"],"type":"string"},"user":{"description":"Postgres user name","type":"string"}},"required":["type"],"title":"SourceUpdateConnectionParams-Postgres","type":"object"},{"additionalProperties":false,"properties":{"database":{"description":"Redshift database name","type":"string"},"host":{"description":"Redshift host","type":"string"},"password":{"description":"Redshift password","type":"string"},"port":{"description":"Redshift port","nullable":true,"type":"integer"},"type":{"enum":["redshift"],"type":"string"},"user":{"description":"Redshift user name","type":"string"}},"required":["type"],"title":"SourceUpdateConnectionParams-AmazonRedshift","type":"object"},{"additionalProperties":false,"properties":{"s3_uri":{"description":"S3 URI","type":"string"},"type":{"enum":["s3_bucket"],"type":"string"}},"required":["type"],"title":"SourceUpdateConnectionParams-S3","type":"object"},{"additionalProperties":false,"properties":{"account_id":{"description":"Snowflake account ID","type":"string"},"database":{"description":"Snowflake database name","type":"string"},"password":{"description":"Snowflake password","type":"string"},"role":{"description":"Snowflake role","nullable":true,"type":"string"},"schema":{"description":"Snowflake database schema","nullable":true,"type":"string"},"type":{"enum":["snowflake"],"type":"string"},"user":{"description":"Snowflake user name","type":"string"},"warehouse":{"description":"Snowflake warehouse","nullable":true,"type":"string"}},"required":["type"],"title":"SourceUpdateConnectionParams-Snowflake","type":"object"},{"additionalProperties":false,"description":"SpatioTemporal Asset Catalogs","properties":{"token":{"description":"STAC token","nullable":true,"type":"string"},"type":{"enum":["stac"],"type":"string"},"url":{"description":"STAC server / asset URL","type":"string"}},"required":["type"],"title":"SourceUpdateConnectionParams-STAC","type":"object"},{"additionalProperties":false,"description":"Web Feature Server","properties":{"type":{"enum":["wfs"],"type":"string"},"url":{"description":"WFS URL","type":"string"}},"required":["type"],"title":"SourceUpdateConnectionParams-WFS","type":"object"},{"additionalProperties":false,"description":"Web Map Service / Web Map Tile Service","properties":{"type":{"enum":["wms_wmts"],"type":"string"},"url":{"description":"WMS/WMTS URL","type":"string"}},"required":["type"],"title":"SourceUpdateConnectionParams-WMS-WMTS","type":"object"}],"title":"SourceUpdateConnectionParams"},"SourcePermissions":{"oneOf":[{"additionalProperties":false,"properties":{"type":{"enum":["workspace_editors"],"type":"string"}},"required":["type"],"title":"SourcePermissions-WorkspaceEditors","type":"object"},{"additionalProperties":false,"properties":{"type":{"enum":["source_owner"],"type":"string"}},"required":["type"],"title":"SourcePermissions-SourceOwner","type":"object"},{"additionalProperties":false,"properties":{"project_ids":{"items":{"$ref":"#/components/schemas/FeltID"},"type":"array"},"type":{"enum":["project_editors"],"type":"string"}},"required":["type","project_ids"],"title":"SourcePermissions-ProjectEditors","type":"object"}],"title":"SourcePermissions"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"SourceReference":{"additionalProperties":false,"properties":{"automatic_sync":{"enum":["enabled","paused"],"type":"string"},"connection_type":{"enum":["abs_bucket","bigquery","databricks","feature_server","gcs_bucket","lightning_server","mssql","postgresql","redshift","s3_bucket","snowflake","stac","wfs","wherobots","wms_wmts"],"type":"string"},"created_at":{"nullable":true,"type":"integer"},"id":{"$ref":"#/components/schemas/FeltID"},"last_synced_at":{"nullable":true,"type":"integer"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"type":"string"},"owner_id":{"$ref":"#/components/schemas/FeltID"},"permissions":{"$ref":"#/components/schemas/SourcePermissions"},"sync_status":{"enum":["syncing","completed","failed"],"type":"string"},"type":{"enum":["source_reference"],"type":"string"},"updated_at":{"nullable":true,"type":"integer"},"workspace_id":{"$ref":"#/components/schemas/FeltID"}},"title":"SourceReference","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/sources/{source_id}/update":{"post":{"callbacks":{},"description":"Update data source connection settings, access permissions, or configuration details.\n\nConnecting the Source and inspecting its datasets will happen asynchronously after the API response is returned. To determine when the inspection process has completed, poll the Show Source endpoint and check for `sync_status: completed`.\n","operationId":"update_source","parameters":[{"description":"The ID of the source to update","in":"path","name":"source_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceUpdateParams"}}},"description":"Source update params","required":false},"responses":{"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceReference"}}},"description":"Source reference"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Update source","tags":["Sources"]}}}}
```

## List sources

> Retrieve all data sources accessible to the authenticated user within the workspace.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Sources connect your databases to Felt.\n\nWith these APIs, you can configure data source connections, credentials, and sync settings to create live maps.\n","name":"Sources"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"SourceReferenceList":{"items":{"$ref":"#/components/schemas/SourceReference"},"title":"SourceReferenceList","type":"array"},"SourceReference":{"additionalProperties":false,"properties":{"automatic_sync":{"enum":["enabled","paused"],"type":"string"},"connection_type":{"enum":["abs_bucket","bigquery","databricks","feature_server","gcs_bucket","lightning_server","mssql","postgresql","redshift","s3_bucket","snowflake","stac","wfs","wherobots","wms_wmts"],"type":"string"},"created_at":{"nullable":true,"type":"integer"},"id":{"$ref":"#/components/schemas/FeltID"},"last_synced_at":{"nullable":true,"type":"integer"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"type":"string"},"owner_id":{"$ref":"#/components/schemas/FeltID"},"permissions":{"$ref":"#/components/schemas/SourcePermissions"},"sync_status":{"enum":["syncing","completed","failed"],"type":"string"},"type":{"enum":["source_reference"],"type":"string"},"updated_at":{"nullable":true,"type":"integer"},"workspace_id":{"$ref":"#/components/schemas/FeltID"}},"title":"SourceReference","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"SourcePermissions":{"oneOf":[{"additionalProperties":false,"properties":{"type":{"enum":["workspace_editors"],"type":"string"}},"required":["type"],"title":"SourcePermissions-WorkspaceEditors","type":"object"},{"additionalProperties":false,"properties":{"type":{"enum":["source_owner"],"type":"string"}},"required":["type"],"title":"SourcePermissions-SourceOwner","type":"object"},{"additionalProperties":false,"properties":{"project_ids":{"items":{"$ref":"#/components/schemas/FeltID"},"type":"array"},"type":{"enum":["project_editors"],"type":"string"}},"required":["type","project_ids"],"title":"SourcePermissions-ProjectEditors","type":"object"}],"title":"SourcePermissions"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/sources":{"get":{"callbacks":{},"description":"Retrieve all data sources accessible to the authenticated user within the workspace.","operationId":"list_sources","parameters":[{"description":"Only needed when using the API as part of a plugin","in":"query","name":"workspace_id","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceReferenceList"}}},"description":"Source references"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"List sources","tags":["Sources"]}}}}
```

## Create source

> Create a new data source connection with authentication credentials and access permissions.\
> \
> Connecting the Source and inspecting its datasets will happen asynchronously after the API response is returned. To determine when the inspection process has completed, poll the Show Source endpoint and check for \`sync\_status: completed\`.<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Sources connect your databases to Felt.\n\nWith these APIs, you can configure data source connections, credentials, and sync settings to create live maps.\n","name":"Sources"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"SourceCreateParams":{"additionalProperties":false,"properties":{"connection":{"$ref":"#/components/schemas/SourceCreateConnectionParams"},"name":{"type":"string"},"permissions":{"$ref":"#/components/schemas/SourcePermissions"}},"required":["name","connection"],"title":"SourceCreateParams","type":"object"},"SourceCreateConnectionParams":{"oneOf":[{"additionalProperties":false,"properties":{"blob_storage_url":{"description":"ABS blob storage URL","type":"string"},"credentials":{"items":{"properties":{"credential":{"$ref":"#/components/schemas/SourceCredential-AzureStorageString"},"name":{"type":"string"},"use_case":{"enum":["source_authentication"],"type":"string"}},"required":["credential","use_case","name"],"type":"object"},"maxItems":1,"type":"array"},"type":{"enum":["abs_bucket"],"type":"string"}},"required":["type","blob_storage_url"],"title":"SourceCreateConnectionParams-ABS","type":"object"},{"additionalProperties":false,"properties":{"base64_encoded_service_account":{"description":"BigQuery credentials - Base 64 encoded Service account JSON","nullable":true,"type":"string"},"dataset":{"description":"BigQuery dataset","nullable":true,"type":"string"},"project":{"description":"BigQuery project","type":"string"},"type":{"enum":["bigquery"],"type":"string"}},"required":["type","project"],"title":"SourceCreateConnectionParams-GoogleBigQuery","type":"object"},{"additionalProperties":false,"properties":{"catalog":{"description":"Databricks catalog","nullable":true,"type":"string"},"credentials":{"items":{"properties":{"credential":{"oneOf":[{"$ref":"#/components/schemas/SourceCredential-DatabricksPAT"},{"$ref":"#/components/schemas/SourceCredential-OAuthM2M"}],"type":"object"},"name":{"type":"string"},"use_case":{"enum":["source_authentication"],"type":"string"}},"required":["credential","use_case","name"],"type":"object"},"maxItems":1,"type":"array"},"http_path":{"description":"Databricks server HTTP path","type":"string"},"schema":{"description":"Databricks schema","nullable":true,"type":"string"},"server_hostname":{"description":"Databricks server hostname","type":"string"},"type":{"enum":["databricks"],"type":"string"}},"required":["type","server_hostname","http_path"],"title":"SourceCreateConnectionParams-Databricks","type":"object"},{"additionalProperties":false,"properties":{"token":{"description":"ESRI server token","nullable":true,"type":"string"},"type":{"enum":["feature_server"],"type":"string"},"url":{"description":"ESRI FeatureServer, MapServer, or ImageServer URL","type":"string"}},"required":["type","url"],"title":"SourceCreateConnectionParams-ESRIFeatureServer","type":"object"},{"additionalProperties":false,"properties":{"credentials":{"items":{"properties":{"credential":{"$ref":"#/components/schemas/SourceCredential-GcpServiceAccountJson"},"name":{"type":"string"},"use_case":{"enum":["source_authentication"],"type":"string"}},"required":["credential","use_case","name"],"type":"object"},"maxItems":1,"type":"array"},"gs_uri":{"description":"GCS URI","type":"string"},"type":{"enum":["gcs_bucket"],"type":"string"}},"required":["type","gs_uri"],"title":"SourceCreateConnectionParams-GCS","type":"object"},{"additionalProperties":false,"properties":{"database":{"description":"MSSQL database name","type":"string"},"host":{"description":"MSSQL host","type":"string"},"password":{"description":"MSSQL password","type":"string"},"port":{"description":"MSSQL port","nullable":true,"type":"integer"},"type":{"enum":["mssql"],"type":"string"},"user":{"description":"MSSQL user name","type":"string"}},"required":["type","host","database","user","password"],"title":"SourceCreateConnectionParams-MicrosoftSQL","type":"object"},{"additionalProperties":false,"description":"Postgres / PostGIS","properties":{"database":{"description":"Postgres database name","type":"string"},"host":{"description":"Postgres host","type":"string"},"password":{"description":"Postgres password","type":"string"},"port":{"description":"Postgres port","nullable":true,"type":"integer"},"schema":{"description":"Postgres schema","type":"string"},"type":{"enum":["postgresql"],"type":"string"},"user":{"description":"Postgres user name","type":"string"}},"required":["type","host","database","user","password"],"title":"SourceCreateConnectionParams-Postgres","type":"object"},{"additionalProperties":false,"properties":{"database":{"description":"Redshift database name","type":"string"},"host":{"description":"Redshift host","type":"string"},"password":{"description":"Redshift password","type":"string"},"port":{"description":"Redshift port","nullable":true,"type":"integer"},"type":{"enum":["redshift"],"type":"string"},"user":{"description":"Redshift user name","type":"string"}},"required":["type","host","database","user","password"],"title":"SourceCreateConnectionParams-AmazonRedshift","type":"object"},{"additionalProperties":false,"properties":{"credentials":{"items":{"properties":{"credential":{"$ref":"#/components/schemas/SourceCredential-AwsAssumeRole"},"name":{"type":"string"},"use_case":{"enum":["source_authentication"],"type":"string"}},"required":["credential","use_case","name"],"type":"object"},"maxItems":1,"type":"array"},"s3_uri":{"description":"S3 URI","type":"string"},"type":{"enum":["s3_bucket"],"type":"string"}},"required":["type","s3_uri"],"title":"SourceCreateConnectionParams-S3","type":"object"},{"additionalProperties":false,"properties":{"account_id":{"description":"Snowflake account ID","type":"string"},"credentials":{"items":{"properties":{"credential":{"oneOf":[{"$ref":"#/components/schemas/SourceCredential-KeyPair"},{"$ref":"#/components/schemas/SourceCredential-SnowflakePAT"}],"type":"object"},"name":{"type":"string"},"use_case":{"enum":["source_authentication"],"type":"string"}},"required":["credential","use_case","name"],"type":"object"},"maxItems":1,"type":"array"},"database":{"description":"Snowflake database name","type":"string"},"password":{"description":"Snowflake password","type":"string"},"role":{"description":"Snowflake role","nullable":true,"type":"string"},"schema":{"description":"Snowflake database schema","nullable":true,"type":"string"},"type":{"enum":["snowflake"],"type":"string"},"user":{"description":"Snowflake user name","type":"string"},"warehouse":{"description":"Snowflake warehouse","nullable":true,"type":"string"}},"required":["type","database","account_id","user"],"title":"SourceCreateConnectionParams-Snowflake","type":"object"},{"additionalProperties":false,"description":"SpatioTemporal Asset Catalogs","properties":{"credentials":{"items":{"properties":{"credential":{"oneOf":[{"$ref":"#/components/schemas/SourceCredential-AwsAssumeRole"},{"$ref":"#/components/schemas/SourceCredential-AzureStorageString"},{"$ref":"#/components/schemas/SourceCredential-GcpServiceAccountJson"},{"$ref":"#/components/schemas/SourceCredential-CustomHeaders"}],"type":"object"},"name":{"type":"string"},"use_case":{"enum":["stac_api_authentication","stac_asset_fetching"],"type":"string"}},"required":["name","use_case","credential"],"type":"object"},"type":"array"},"token":{"description":"STAC token","nullable":true,"type":"string"},"type":{"enum":["stac"],"type":"string"},"url":{"description":"STAC server / asset URL","type":"string"}},"required":["type","url"],"title":"SourceCreateConnectionParams-STAC","type":"object"},{"additionalProperties":false,"description":"Web Feature Server","properties":{"type":{"enum":["wfs"],"type":"string"},"url":{"description":"WFS URL","type":"string"}},"required":["type","url"],"title":"SourceCreateConnectionParams-WFS","type":"object"},{"additionalProperties":false,"description":"Web Map Service / Web Map Tile Service","properties":{"type":{"enum":["wms_wmts"],"type":"string"},"url":{"description":"WMS/WMTS URL","type":"string"}},"required":["type","url"],"title":"SourceCreateConnectionParams-WMSWMTS","type":"object"}],"title":"SourceCreateConnectionParams"},"SourceCredential-AzureStorageString":{"additionalProperties":false,"description":"Authenticate to Azure Blob Storage","properties":{"connection_string":{"type":"string"},"type":{"enum":["azure_storage_connection_string"],"type":"string"}},"required":["type","connection_string"],"title":"SourceCredential-AzureStorageString","type":"object"},"SourceCredential-DatabricksPAT":{"additionalProperties":false,"description":"Authenticate to Databricks using a Personal Access Token (PAT)","properties":{"token":{"type":"string"},"type":{"enum":["databricks_pat"],"type":"string"}},"required":["type","token"],"title":"SourceCredential-DatabricksPAT","type":"object"},"SourceCredential-OAuthM2M":{"additionalProperties":false,"description":"Authenticate to your source using Machine-to-Machine (M2M) OAuth","properties":{"client_id":{"type":"string"},"client_secret":{"type":"string"},"type":{"enum":["oauth_m2m"],"type":"string"}},"required":["type","client_id","client_secret"],"title":"SourceCredential-OAuthM2M","type":"object"},"SourceCredential-GcpServiceAccountJson":{"additionalProperties":false,"properties":{"service_account_filename":{"type":"string"},"service_account_json":{"oneOf":[{"type":"object"},{"type":"string"}]},"type":{"enum":["gcp_service_account_json"],"type":"string"}},"required":["type","service_account_filename","service_account_json"],"title":"SourceCredential-GcpServiceAccountJson","type":"object"},"SourceCredential-AwsAssumeRole":{"additionalProperties":false,"description":"Authenticate to AWS S3","properties":{"role_arn":{"type":"string"},"role_session_name":{"type":"string"},"type":{"enum":["aws_assume_role"],"type":"string"}},"required":["type","role_arn","role_session_name"],"title":"SourceCredential-AwsAssumeRole","type":"object"},"SourceCredential-KeyPair":{"additionalProperties":false,"description":"Authenticate using key pair authentication","properties":{"private_key":{"type":"string"},"private_key_name":{"type":"string"},"private_key_passphrase":{"type":"string"},"type":{"enum":["key_pair"],"type":"string"}},"required":["type","private_key","private_key_name"],"title":"SourceCredential-KeyPair","type":"object"},"SourceCredential-SnowflakePAT":{"additionalProperties":false,"description":"Authenticate to Snowflake with a programmatic access token (PAT)","properties":{"token":{"type":"string"},"type":{"enum":["snowflake_pat"],"type":"string"}},"required":["type","token"],"title":"SourceCredential-SnowflakePAT","type":"object"},"SourceCredential-CustomHeaders":{"additionalProperties":false,"description":"Authenticate to an API using headers","properties":{"headers":{"items":{"properties":{"name":{"description":"The header name","type":"string"},"sensitive":{"description":"Whether or not the header is sensitive. If it is marked as sensitive, then `felt:redacted` will be returned when viewing this header","type":"boolean"},"value":{"description":"The header value","type":"string"}},"required":["name","value","sensitive"],"type":"object"},"type":"array"},"type":{"enum":["custom_headers"],"type":"string"}},"required":["type","headers"],"title":"SourceCredential-CustomHeaders","type":"object"},"SourcePermissions":{"oneOf":[{"additionalProperties":false,"properties":{"type":{"enum":["workspace_editors"],"type":"string"}},"required":["type"],"title":"SourcePermissions-WorkspaceEditors","type":"object"},{"additionalProperties":false,"properties":{"type":{"enum":["source_owner"],"type":"string"}},"required":["type"],"title":"SourcePermissions-SourceOwner","type":"object"},{"additionalProperties":false,"properties":{"project_ids":{"items":{"$ref":"#/components/schemas/FeltID"},"type":"array"},"type":{"enum":["project_editors"],"type":"string"}},"required":["type","project_ids"],"title":"SourcePermissions-ProjectEditors","type":"object"}],"title":"SourcePermissions"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"SourceReference":{"additionalProperties":false,"properties":{"automatic_sync":{"enum":["enabled","paused"],"type":"string"},"connection_type":{"enum":["abs_bucket","bigquery","databricks","feature_server","gcs_bucket","lightning_server","mssql","postgresql","redshift","s3_bucket","snowflake","stac","wfs","wherobots","wms_wmts"],"type":"string"},"created_at":{"nullable":true,"type":"integer"},"id":{"$ref":"#/components/schemas/FeltID"},"last_synced_at":{"nullable":true,"type":"integer"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"type":"string"},"owner_id":{"$ref":"#/components/schemas/FeltID"},"permissions":{"$ref":"#/components/schemas/SourcePermissions"},"sync_status":{"enum":["syncing","completed","failed"],"type":"string"},"type":{"enum":["source_reference"],"type":"string"},"updated_at":{"nullable":true,"type":"integer"},"workspace_id":{"$ref":"#/components/schemas/FeltID"}},"title":"SourceReference","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/sources":{"post":{"callbacks":{},"description":"Create a new data source connection with authentication credentials and access permissions.\n\nConnecting the Source and inspecting its datasets will happen asynchronously after the API response is returned. To determine when the inspection process has completed, poll the Show Source endpoint and check for `sync_status: completed`.\n","operationId":"create_source","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceCreateParams"}}},"description":"Source create params","required":false},"responses":{"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceReference"}}},"description":"Source reference"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Create source","tags":["Sources"]}}}}
```

## Sync source

> Trigger a full data synchronization from the source to update all connected layers with latest data.\
> \
> Syncing will happen asynchronously after the API response is returned. To determine when the inspection process has completed, poll the Show Source endpoint and check for \`sync\_status: completed\`.<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Sources connect your databases to Felt.\n\nWith these APIs, you can configure data source connections, credentials, and sync settings to create live maps.\n","name":"Sources"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"SourceReference":{"additionalProperties":false,"properties":{"automatic_sync":{"enum":["enabled","paused"],"type":"string"},"connection_type":{"enum":["abs_bucket","bigquery","databricks","feature_server","gcs_bucket","lightning_server","mssql","postgresql","redshift","s3_bucket","snowflake","stac","wfs","wherobots","wms_wmts"],"type":"string"},"created_at":{"nullable":true,"type":"integer"},"id":{"$ref":"#/components/schemas/FeltID"},"last_synced_at":{"nullable":true,"type":"integer"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"name":{"type":"string"},"owner_id":{"$ref":"#/components/schemas/FeltID"},"permissions":{"$ref":"#/components/schemas/SourcePermissions"},"sync_status":{"enum":["syncing","completed","failed"],"type":"string"},"type":{"enum":["source_reference"],"type":"string"},"updated_at":{"nullable":true,"type":"integer"},"workspace_id":{"$ref":"#/components/schemas/FeltID"}},"title":"SourceReference","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"SourcePermissions":{"oneOf":[{"additionalProperties":false,"properties":{"type":{"enum":["workspace_editors"],"type":"string"}},"required":["type"],"title":"SourcePermissions-WorkspaceEditors","type":"object"},{"additionalProperties":false,"properties":{"type":{"enum":["source_owner"],"type":"string"}},"required":["type"],"title":"SourcePermissions-SourceOwner","type":"object"},{"additionalProperties":false,"properties":{"project_ids":{"items":{"$ref":"#/components/schemas/FeltID"},"type":"array"},"type":{"enum":["project_editors"],"type":"string"}},"required":["type","project_ids"],"title":"SourcePermissions-ProjectEditors","type":"object"}],"title":"SourcePermissions"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/sources/{source_id}/sync":{"post":{"callbacks":{},"description":"Trigger a full data synchronization from the source to update all connected layers with latest data.\n\nSyncing will happen asynchronously after the API response is returned. To determine when the inspection process has completed, poll the Show Source endpoint and check for `sync_status: completed`.\n","operationId":"sync_source","parameters":[{"description":"The ID of the source to sync","in":"path","name":"source_id","required":true,"schema":{"type":"string"}}],"responses":{"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceReference"}}},"description":"Source reference"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Sync source","tags":["Sources"]}}}}
```

## Get source

> Retrieve detailed configuration and connection information for a specific data source.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Sources connect your databases to Felt.\n\nWith these APIs, you can configure data source connections, credentials, and sync settings to create live maps.\n","name":"Sources"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"Source":{"additionalProperties":false,"properties":{"automatic_sync":{"enum":["enabled","paused"],"type":"string"},"connection":{"nullable":true,"oneOf":[{"$ref":"#/components/schemas/SourceConnection-ABSBucket"},{"$ref":"#/components/schemas/SourceConnection-Google-BigQuery"},{"$ref":"#/components/schemas/SourceConnection-Databricks"},{"$ref":"#/components/schemas/SourceConnection-ESRI-FeatureServer"},{"$ref":"#/components/schemas/SourceConnection-GCSBucket"},{"$ref":"#/components/schemas/SourceConnection-Microsoft-SQL"},{"$ref":"#/components/schemas/SourceConnection-Postgres"},{"$ref":"#/components/schemas/SourceConnection-Amazon-Redshift"},{"$ref":"#/components/schemas/SourceConnection-S3Bucket"},{"$ref":"#/components/schemas/SourceConnection-Snowflake"},{"$ref":"#/components/schemas/SourceConnection-STAC"},{"$ref":"#/components/schemas/SourceConnection-WFS"},{"$ref":"#/components/schemas/SourceConnection-WMS-WMTS"}]},"created_at":{"nullable":true,"type":"integer"},"datasets":{"items":{"$ref":"#/components/schemas/SourceDataset"},"type":"array"},"id":{"$ref":"#/components/schemas/FeltID"},"last_synced_at":{"nullable":true,"type":"integer"},"name":{"type":"string"},"owner_id":{"$ref":"#/components/schemas/FeltID"},"permissions":{"$ref":"#/components/schemas/SourcePermissions"},"sync_status":{"enum":["syncing","completed","failed"],"type":"string"},"type":{"enum":["source"],"type":"string"},"updated_at":{"nullable":true,"type":"integer"},"workspace_id":{"$ref":"#/components/schemas/FeltID"}},"title":"Source","type":"object"},"SourceConnection-ABSBucket":{"additionalProperties":false,"description":"Microsoft Azure Blob Storage","properties":{"blob_storage_url":{"type":"string"},"credentials":{"items":{"properties":{"created_at":{"nullable":true,"type":"integer"},"credential":{"oneOf":[{"$ref":"#/components/schemas/SourceCredential-AzureStorageString"}],"type":"object"},"id":{"$ref":"#/components/schemas/FeltID"},"name":{"type":"string"},"source_id":{"$ref":"#/components/schemas/FeltID"},"updated_at":{"nullable":true,"type":"integer"},"use_case":{"enum":["source_authentication"],"type":"string"}},"type":"object"},"type":"array"},"type":{"enum":["abs_bucket"],"type":"string"}},"title":"SourceConnection-ABSBucket","type":"object"},"SourceCredential-AzureStorageString":{"additionalProperties":false,"description":"Authenticate to Azure Blob Storage","properties":{"connection_string":{"type":"string"},"type":{"enum":["azure_storage_connection_string"],"type":"string"}},"required":["type","connection_string"],"title":"SourceCredential-AzureStorageString","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"SourceConnection-Google-BigQuery":{"additionalProperties":false,"properties":{"dataset":{"description":"BigQuery dataset to index. If omitted all datasets will be indexed","nullable":true,"type":"string"},"project":{"description":"BigQuery project to index","type":"string"},"type":{"enum":["bigquery"],"type":"string"}},"title":"SourceConnection-Google-BigQuery","type":"object"},"SourceConnection-Databricks":{"additionalProperties":false,"properties":{"catalog":{"nullable":true,"type":"string"},"credentials":{"items":{"properties":{"created_at":{"nullable":true,"type":"integer"},"credential":{"oneOf":[{"$ref":"#/components/schemas/SourceCredential-OAuthM2M"},{"$ref":"#/components/schemas/SourceCredential-DatabricksPAT"}],"type":"object"},"id":{"$ref":"#/components/schemas/FeltID"},"name":{"type":"string"},"source_id":{"$ref":"#/components/schemas/FeltID"},"updated_at":{"nullable":true,"type":"integer"},"use_case":{"enum":["source_authentication"],"type":"string"}},"type":"object"},"type":"array"},"http_path":{"type":"string"},"schema":{"nullable":true,"type":"string"},"server_hostname":{"type":"string"},"type":{"enum":["databricks"],"type":"string"}},"title":"SourceConnection-Databricks","type":"object"},"SourceCredential-OAuthM2M":{"additionalProperties":false,"description":"Authenticate to your source using Machine-to-Machine (M2M) OAuth","properties":{"client_id":{"type":"string"},"client_secret":{"type":"string"},"type":{"enum":["oauth_m2m"],"type":"string"}},"required":["type","client_id","client_secret"],"title":"SourceCredential-OAuthM2M","type":"object"},"SourceCredential-DatabricksPAT":{"additionalProperties":false,"description":"Authenticate to Databricks using a Personal Access Token (PAT)","properties":{"token":{"type":"string"},"type":{"enum":["databricks_pat"],"type":"string"}},"required":["type","token"],"title":"SourceCredential-DatabricksPAT","type":"object"},"SourceConnection-ESRI-FeatureServer":{"additionalProperties":false,"properties":{"type":{"enum":["feature_server"],"type":"string"},"url":{"type":"string"}},"title":"SourceConnection-ESRI-FeatureServer","type":"object"},"SourceConnection-GCSBucket":{"additionalProperties":false,"description":"Google Cloud Storage","properties":{"credentials":{"items":{"properties":{"created_at":{"nullable":true,"type":"integer"},"credential":{"oneOf":[{"$ref":"#/components/schemas/SourceCredential-GcpServiceAccountJson"}],"type":"object"},"id":{"$ref":"#/components/schemas/FeltID"},"name":{"type":"string"},"source_id":{"$ref":"#/components/schemas/FeltID"},"updated_at":{"nullable":true,"type":"integer"},"use_case":{"enum":["source_authentication"],"type":"string"}},"type":"object"},"type":"array"},"gs_uri":{"type":"string"},"type":{"enum":["gcs_bucket"],"type":"string"}},"title":"SourceConnection-GCSBucket","type":"object"},"SourceCredential-GcpServiceAccountJson":{"additionalProperties":false,"properties":{"service_account_filename":{"type":"string"},"service_account_json":{"oneOf":[{"type":"object"},{"type":"string"}]},"type":{"enum":["gcp_service_account_json"],"type":"string"}},"required":["type","service_account_filename","service_account_json"],"title":"SourceCredential-GcpServiceAccountJson","type":"object"},"SourceConnection-Microsoft-SQL":{"additionalProperties":false,"properties":{"database":{"type":"string"},"host":{"type":"string"},"port":{"nullable":true,"type":"integer"},"type":{"enum":["mssql"],"type":"string"},"user":{"type":"string"}},"title":"SourceConnection-Microsoft-SQL","type":"object"},"SourceConnection-Postgres":{"additionalProperties":false,"description":"Postgres / PostGIS","properties":{"database":{"type":"string"},"host":{"type":"string"},"port":{"nullable":true,"type":"integer"},"schema":{"nullable":true,"type":"string"},"type":{"enum":["postgresql"],"type":"string"},"user":{"type":"string"}},"title":"SourceConnection-Postgres","type":"object"},"SourceConnection-Amazon-Redshift":{"additionalProperties":false,"properties":{"database":{"type":"string"},"host":{"type":"string"},"port":{"nullable":true,"type":"integer"},"type":{"enum":["redshift"],"type":"string"},"user":{"type":"string"}},"title":"SourceConnection-Amazon-Redshift","type":"object"},"SourceConnection-S3Bucket":{"additionalProperties":false,"description":"AWS S3","properties":{"credentials":{"items":{"properties":{"created_at":{"nullable":true,"type":"integer"},"credential":{"oneOf":[{"$ref":"#/components/schemas/SourceCredential-AwsAssumeRole"}],"type":"object"},"id":{"$ref":"#/components/schemas/FeltID"},"name":{"type":"string"},"source_id":{"$ref":"#/components/schemas/FeltID"},"updated_at":{"nullable":true,"type":"integer"},"use_case":{"enum":["source_authentication"],"type":"string"}},"type":"object"},"type":"array"},"s3_uri":{"type":"string"},"type":{"enum":["s3_bucket"],"type":"string"}},"title":"SourceConnection-S3Bucket","type":"object"},"SourceCredential-AwsAssumeRole":{"additionalProperties":false,"description":"Authenticate to AWS S3","properties":{"role_arn":{"type":"string"},"role_session_name":{"type":"string"},"type":{"enum":["aws_assume_role"],"type":"string"}},"required":["type","role_arn","role_session_name"],"title":"SourceCredential-AwsAssumeRole","type":"object"},"SourceConnection-Snowflake":{"additionalProperties":false,"properties":{"account_id":{"type":"string"},"credentials":{"items":{"properties":{"created_at":{"nullable":true,"type":"integer"},"credential":{"oneOf":[{"$ref":"#/components/schemas/SourceCredential-SnowflakePAT"},{"$ref":"#/components/schemas/SourceCredential-KeyPair"}],"type":"object"},"id":{"$ref":"#/components/schemas/FeltID"},"name":{"type":"string"},"source_id":{"$ref":"#/components/schemas/FeltID"},"updated_at":{"nullable":true,"type":"integer"},"use_case":{"enum":["source_authentication"],"type":"string"}},"type":"object"},"type":"array"},"database":{"type":"string"},"role":{"nullable":true,"type":"string"},"schema":{"nullable":true,"type":"string"},"type":{"enum":["snowflake"],"type":"string"},"user":{"type":"string"},"warehouse":{"nullable":true,"type":"string"}},"title":"SourceConnection-Snowflake","type":"object"},"SourceCredential-SnowflakePAT":{"additionalProperties":false,"description":"Authenticate to Snowflake with a programmatic access token (PAT)","properties":{"token":{"type":"string"},"type":{"enum":["snowflake_pat"],"type":"string"}},"required":["type","token"],"title":"SourceCredential-SnowflakePAT","type":"object"},"SourceCredential-KeyPair":{"additionalProperties":false,"description":"Authenticate using key pair authentication","properties":{"private_key":{"type":"string"},"private_key_name":{"type":"string"},"private_key_passphrase":{"type":"string"},"type":{"enum":["key_pair"],"type":"string"}},"required":["type","private_key","private_key_name"],"title":"SourceCredential-KeyPair","type":"object"},"SourceConnection-STAC":{"additionalProperties":false,"description":"SpatioTemporal Asset Catalogs","properties":{"credentials":{"items":{"properties":{"created_at":{"nullable":true,"type":"integer"},"credential":{"oneOf":[{"$ref":"#/components/schemas/SourceCredential-GcpServiceAccountJson"},{"$ref":"#/components/schemas/SourceCredential-AzureStorageString"},{"$ref":"#/components/schemas/SourceCredential-AwsAssumeRole"},{"$ref":"#/components/schemas/SourceCredential-CustomHeaders"}],"type":"object"},"id":{"$ref":"#/components/schemas/FeltID"},"name":{"type":"string"},"source_id":{"$ref":"#/components/schemas/FeltID"},"updated_at":{"nullable":true,"type":"integer"},"use_case":{"enum":["stac_api_authentication","stac_asset_fetching"],"type":"string"}},"type":"object"},"type":"array"},"type":{"enum":["stac"],"type":"string"},"url":{"type":"string"}},"title":"SourceConnection-STAC","type":"object"},"SourceCredential-CustomHeaders":{"additionalProperties":false,"description":"Authenticate to an API using headers","properties":{"headers":{"items":{"properties":{"name":{"description":"The header name","type":"string"},"sensitive":{"description":"Whether or not the header is sensitive. If it is marked as sensitive, then `felt:redacted` will be returned when viewing this header","type":"boolean"},"value":{"description":"The header value","type":"string"}},"required":["name","value","sensitive"],"type":"object"},"type":"array"},"type":{"enum":["custom_headers"],"type":"string"}},"required":["type","headers"],"title":"SourceCredential-CustomHeaders","type":"object"},"SourceConnection-WFS":{"additionalProperties":false,"description":"Web Feature Server","properties":{"type":{"enum":["wfs"],"type":"string"},"url":{"type":"string"}},"title":"SourceConnection-WFS","type":"object"},"SourceConnection-WMS-WMTS":{"additionalProperties":false,"description":"Web Map Service / Web Map Tile Service","properties":{"type":{"enum":["wms_wmts"],"type":"string"},"url":{"type":"string"}},"title":"SourceConnection-WMS-WMTS","type":"object"},"SourceDataset":{"additionalProperties":false,"description":"A Dataset found when inspecting a Source, e.g. a database table.","properties":{"created_at":{"type":"integer"},"description":{"nullable":true,"type":"string"},"geometry_type":{"enum":["polygon","line","point","raster","none"],"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"inspection_status":{"enum":["completed","failed"],"type":"string"},"name":{"type":"string"},"type":{"enum":["dataset"],"type":"string"},"updated_at":{"type":"integer"}},"title":"SourceDataset","type":"object"},"SourcePermissions":{"oneOf":[{"additionalProperties":false,"properties":{"type":{"enum":["workspace_editors"],"type":"string"}},"required":["type"],"title":"SourcePermissions-WorkspaceEditors","type":"object"},{"additionalProperties":false,"properties":{"type":{"enum":["source_owner"],"type":"string"}},"required":["type"],"title":"SourcePermissions-SourceOwner","type":"object"},{"additionalProperties":false,"properties":{"project_ids":{"items":{"$ref":"#/components/schemas/FeltID"},"type":"array"},"type":{"enum":["project_editors"],"type":"string"}},"required":["type","project_ids"],"title":"SourcePermissions-ProjectEditors","type":"object"}],"title":"SourcePermissions"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/sources/{source_id}":{"get":{"callbacks":{},"description":"Retrieve detailed configuration and connection information for a specific data source.","operationId":"show_source","parameters":[{"description":"The ID of the source to show","in":"path","name":"source_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Source"}}},"description":"Source"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Get source","tags":["Sources"]}}}}
```

## Delete source

> Permanently delete a data source connection and all its associated layers and data.\
> \
> {% hint style="warning" %}\
> Any layers created from the Source will remain after it is deleted, but they will no longer be refreshed.\
> {% endhint %}<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Sources connect your databases to Felt.\n\nWith these APIs, you can configure data source connections, credentials, and sync settings to create live maps.\n","name":"Sources"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/sources/{source_id}":{"delete":{"callbacks":{},"description":"Permanently delete a data source connection and all its associated layers and data.\n\n{% hint style=\"warning\" %}\nAny layers created from the Source will remain after it is deleted, but they will no longer be refreshed.\n{% endhint %}\n","operationId":"delete_source","parameters":[{"description":"The ID of the source to delete","in":"path","name":"source_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Delete source","tags":["Sources"]}}}}
```

## Create source credential

> Add authentication credentials to an existing data source for secure access.\
> \
> Some sources may need to be configured with additional credentials to work with Felt. Access to S3 Buckets, for example, may be protected by IAM policies. Adding a \`SourceCredential-AwsAssumeRole\` credential to your S3 Bucket source allows Felt to connect to a private source.\
> \
> Sensitive fields in credentials, like \`SourceCredential-KeyPair.private\_key\`, will be returned as \`felt:redacted\`.<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Sources connect your databases to Felt.\n\nWith these APIs, you can configure data source connections, credentials, and sync settings to create live maps.\n","name":"Sources"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"SourceCredentialCreateParams":{"additionalProperties":false,"properties":{"credential":{"oneOf":[{"$ref":"#/components/schemas/SourceCredential-AwsAssumeRole"},{"$ref":"#/components/schemas/SourceCredential-AzureStorageString"},{"$ref":"#/components/schemas/SourceCredential-CustomHeaders"},{"$ref":"#/components/schemas/SourceCredential-GcpServiceAccountJson"},{"$ref":"#/components/schemas/SourceCredential-KeyPair"},{"$ref":"#/components/schemas/SourceCredential-SnowflakePAT"}],"type":"object"},"name":{"type":"string"},"use_case":{"enum":["stac_api_authentication","stac_asset_fetching","source_authentication"],"type":"string"}},"required":["name","use_case","credential"],"title":"SourceCredentialCreateParams","type":"object"},"SourceCredential-AwsAssumeRole":{"additionalProperties":false,"description":"Authenticate to AWS S3","properties":{"role_arn":{"type":"string"},"role_session_name":{"type":"string"},"type":{"enum":["aws_assume_role"],"type":"string"}},"required":["type","role_arn","role_session_name"],"title":"SourceCredential-AwsAssumeRole","type":"object"},"SourceCredential-AzureStorageString":{"additionalProperties":false,"description":"Authenticate to Azure Blob Storage","properties":{"connection_string":{"type":"string"},"type":{"enum":["azure_storage_connection_string"],"type":"string"}},"required":["type","connection_string"],"title":"SourceCredential-AzureStorageString","type":"object"},"SourceCredential-CustomHeaders":{"additionalProperties":false,"description":"Authenticate to an API using headers","properties":{"headers":{"items":{"properties":{"name":{"description":"The header name","type":"string"},"sensitive":{"description":"Whether or not the header is sensitive. If it is marked as sensitive, then `felt:redacted` will be returned when viewing this header","type":"boolean"},"value":{"description":"The header value","type":"string"}},"required":["name","value","sensitive"],"type":"object"},"type":"array"},"type":{"enum":["custom_headers"],"type":"string"}},"required":["type","headers"],"title":"SourceCredential-CustomHeaders","type":"object"},"SourceCredential-GcpServiceAccountJson":{"additionalProperties":false,"properties":{"service_account_filename":{"type":"string"},"service_account_json":{"oneOf":[{"type":"object"},{"type":"string"}]},"type":{"enum":["gcp_service_account_json"],"type":"string"}},"required":["type","service_account_filename","service_account_json"],"title":"SourceCredential-GcpServiceAccountJson","type":"object"},"SourceCredential-KeyPair":{"additionalProperties":false,"description":"Authenticate using key pair authentication","properties":{"private_key":{"type":"string"},"private_key_name":{"type":"string"},"private_key_passphrase":{"type":"string"},"type":{"enum":["key_pair"],"type":"string"}},"required":["type","private_key","private_key_name"],"title":"SourceCredential-KeyPair","type":"object"},"SourceCredential-SnowflakePAT":{"additionalProperties":false,"description":"Authenticate to Snowflake with a programmatic access token (PAT)","properties":{"token":{"type":"string"},"type":{"enum":["snowflake_pat"],"type":"string"}},"required":["type","token"],"title":"SourceCredential-SnowflakePAT","type":"object"},"SourceCredential":{"additionalProperties":false,"properties":{"created_at":{"nullable":true,"type":"integer"},"credential":{"oneOf":[{"$ref":"#/components/schemas/SourceCredential-AwsAssumeRole"},{"$ref":"#/components/schemas/SourceCredential-AzureStorageString"},{"$ref":"#/components/schemas/SourceCredential-CustomHeaders"},{"$ref":"#/components/schemas/SourceCredential-GcpServiceAccountJson"},{"$ref":"#/components/schemas/SourceCredential-KeyPair"},{"$ref":"#/components/schemas/SourceCredential-SnowflakePAT"}],"type":"object"},"id":{"$ref":"#/components/schemas/FeltID"},"name":{"type":"string"},"source_id":{"$ref":"#/components/schemas/FeltID"},"updated_at":{"nullable":true,"type":"integer"},"use_case":{"enum":["stac_api_authentication","stac_asset_fetching","source_authentication"],"type":"string"}},"required":["id","source_id","name","use_case","created_at","updated_at","credential"],"title":"SourceCredential","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/sources/{source_id}/credentials":{"post":{"callbacks":{},"description":"Add authentication credentials to an existing data source for secure access.\n\nSome sources may need to be configured with additional credentials to work with Felt. Access to S3 Buckets, for example, may be protected by IAM policies. Adding a `SourceCredential-AwsAssumeRole` credential to your S3 Bucket source allows Felt to connect to a private source.\n\nSensitive fields in credentials, like `SourceCredential-KeyPair.private_key`, will be returned as `felt:redacted`.\n","operationId":"create_source_credential","parameters":[{"description":"The ID of the source to attach the credential","in":"path","name":"source_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceCredentialCreateParams"}}},"description":"Source create credential params","required":false},"responses":{"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceCredential"}}},"description":"Source credential created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Create source credential","tags":["Sources"]}}}}
```

## Update source credential

> Update existing authentication credentials for a data source connection.\
> \
> Sensitive fields in credentials, like \`SourceCredential-KeyPair.private\_key\`, will be returned as \`felt:redacted\`.<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Sources connect your databases to Felt.\n\nWith these APIs, you can configure data source connections, credentials, and sync settings to create live maps.\n","name":"Sources"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"SourceCredentialUpdateParams":{"additionalProperties":false,"properties":{"credential":{"oneOf":[{"$ref":"#/components/schemas/SourceCredentialUpdate-AwsAssumeRole"},{"$ref":"#/components/schemas/SourceCredentialUpdate-AzureStorageString"},{"$ref":"#/components/schemas/SourceCredentialUpdate-CustomHeaders"},{"$ref":"#/components/schemas/SourceCredentialUpdate-GcpServiceAccountJson"},{"$ref":"#/components/schemas/SourceCredentialUpdate-KeyPair"},{"$ref":"#/components/schemas/SourceCredentialUpdate-SnowflakePAT"}],"type":"object"},"name":{"type":"string"},"use_case":{"enum":["stac_api_authentication","stac_asset_fetching","source_authentication"],"type":"string"}},"title":"SourceCredentialUpdateParams","type":"object"},"SourceCredentialUpdate-AwsAssumeRole":{"additionalProperties":false,"description":"Authenticate to AWS S3","properties":{"role_arn":{"type":"string"},"role_session_name":{"type":"string"},"type":{"enum":["aws_assume_role"],"type":"string"}},"required":["type"],"title":"SourceCredentialUpdate-AwsAssumeRole","type":"object"},"SourceCredentialUpdate-AzureStorageString":{"additionalProperties":false,"description":"Authenticate to Azure Blob Storage","properties":{"connection_string":{"type":"string"},"type":{"enum":["azure_storage_connection_string"],"type":"string"}},"required":["type"],"title":"SourceCredentialUpdate-AzureStorageString","type":"object"},"SourceCredentialUpdate-CustomHeaders":{"additionalProperties":false,"description":"Authenticate to an API using headers","properties":{"headers":{"items":{"properties":{"name":{"description":"The header name","type":"string"},"sensitive":{"description":"Whether or not the header is sensitive. If it is marked as sensitive, then `felt:redacted` will be returned when viewing this header","type":"boolean"},"value":{"description":"The header value","type":"string"}},"required":["name","value","sensitive"],"type":"object"},"type":"array"},"type":{"enum":["custom_headers"],"type":"string"}},"required":["type"],"title":"SourceCredentialUpdate-CustomHeaders","type":"object"},"SourceCredentialUpdate-GcpServiceAccountJson":{"additionalProperties":false,"properties":{"service_account_filename":{"type":"string"},"service_account_json":{"oneOf":[{"type":"object"},{"type":"string"}]},"type":{"enum":["gcp_service_account_json"],"type":"string"}},"required":["type"],"title":"SourceCredentialUpdate-GcpServiceAccountJson","type":"object"},"SourceCredentialUpdate-KeyPair":{"additionalProperties":false,"description":"Authenticate using key pair authentication","properties":{"private_key":{"type":"string"},"private_key_name":{"type":"string"},"private_key_passphrase":{"type":"string"},"type":{"enum":["key_pair"],"type":"string"}},"required":["type"],"title":"SourceCredentialUpdate-KeyPair","type":"object"},"SourceCredentialUpdate-SnowflakePAT":{"additionalProperties":false,"description":"Authenticate to Snowflake with a programmatic access token (PAT)","properties":{"token":{"type":"string"},"type":{"enum":["snowflake_pat"],"type":"string"}},"required":["type"],"title":"SourceCredentialUpdate-SnowflakePAT","type":"object"},"SourceCredential":{"additionalProperties":false,"properties":{"created_at":{"nullable":true,"type":"integer"},"credential":{"oneOf":[{"$ref":"#/components/schemas/SourceCredential-AwsAssumeRole"},{"$ref":"#/components/schemas/SourceCredential-AzureStorageString"},{"$ref":"#/components/schemas/SourceCredential-CustomHeaders"},{"$ref":"#/components/schemas/SourceCredential-GcpServiceAccountJson"},{"$ref":"#/components/schemas/SourceCredential-KeyPair"},{"$ref":"#/components/schemas/SourceCredential-SnowflakePAT"}],"type":"object"},"id":{"$ref":"#/components/schemas/FeltID"},"name":{"type":"string"},"source_id":{"$ref":"#/components/schemas/FeltID"},"updated_at":{"nullable":true,"type":"integer"},"use_case":{"enum":["stac_api_authentication","stac_asset_fetching","source_authentication"],"type":"string"}},"required":["id","source_id","name","use_case","created_at","updated_at","credential"],"title":"SourceCredential","type":"object"},"SourceCredential-AwsAssumeRole":{"additionalProperties":false,"description":"Authenticate to AWS S3","properties":{"role_arn":{"type":"string"},"role_session_name":{"type":"string"},"type":{"enum":["aws_assume_role"],"type":"string"}},"required":["type","role_arn","role_session_name"],"title":"SourceCredential-AwsAssumeRole","type":"object"},"SourceCredential-AzureStorageString":{"additionalProperties":false,"description":"Authenticate to Azure Blob Storage","properties":{"connection_string":{"type":"string"},"type":{"enum":["azure_storage_connection_string"],"type":"string"}},"required":["type","connection_string"],"title":"SourceCredential-AzureStorageString","type":"object"},"SourceCredential-CustomHeaders":{"additionalProperties":false,"description":"Authenticate to an API using headers","properties":{"headers":{"items":{"properties":{"name":{"description":"The header name","type":"string"},"sensitive":{"description":"Whether or not the header is sensitive. If it is marked as sensitive, then `felt:redacted` will be returned when viewing this header","type":"boolean"},"value":{"description":"The header value","type":"string"}},"required":["name","value","sensitive"],"type":"object"},"type":"array"},"type":{"enum":["custom_headers"],"type":"string"}},"required":["type","headers"],"title":"SourceCredential-CustomHeaders","type":"object"},"SourceCredential-GcpServiceAccountJson":{"additionalProperties":false,"properties":{"service_account_filename":{"type":"string"},"service_account_json":{"oneOf":[{"type":"object"},{"type":"string"}]},"type":{"enum":["gcp_service_account_json"],"type":"string"}},"required":["type","service_account_filename","service_account_json"],"title":"SourceCredential-GcpServiceAccountJson","type":"object"},"SourceCredential-KeyPair":{"additionalProperties":false,"description":"Authenticate using key pair authentication","properties":{"private_key":{"type":"string"},"private_key_name":{"type":"string"},"private_key_passphrase":{"type":"string"},"type":{"enum":["key_pair"],"type":"string"}},"required":["type","private_key","private_key_name"],"title":"SourceCredential-KeyPair","type":"object"},"SourceCredential-SnowflakePAT":{"additionalProperties":false,"description":"Authenticate to Snowflake with a programmatic access token (PAT)","properties":{"token":{"type":"string"},"type":{"enum":["snowflake_pat"],"type":"string"}},"required":["type","token"],"title":"SourceCredential-SnowflakePAT","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/sources/{source_id}/credentials/{credential_id}/update":{"post":{"callbacks":{},"description":"Update existing authentication credentials for a data source connection.\n\nSensitive fields in credentials, like `SourceCredential-KeyPair.private_key`, will be returned as `felt:redacted`.\n","operationId":"update_source_credential","parameters":[{"description":"The ID of the source that the credential belongs to","in":"path","name":"source_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the credential","in":"path","name":"credential_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceCredentialUpdateParams"}}},"description":"Source credential update params","required":false},"responses":{"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceCredential"}}},"description":"Source credential updated"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Update source credential","tags":["Sources"]}}}}
```

## Delete source credential

> Remove authentication credentials from a data source connection.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Sources connect your databases to Felt.\n\nWith these APIs, you can configure data source connections, credentials, and sync settings to create live maps.\n","name":"Sources"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/sources/{source_id}/credentials/{credential_id}":{"delete":{"callbacks":{},"description":"Remove authentication credentials from a data source connection.","operationId":"delete_source_credential","parameters":[{"description":"The ID of the source that the credential belongs to","in":"path","name":"source_id","required":true,"schema":{"type":"string"}},{"description":"The ID of the credential to delete","in":"path","name":"credential_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Delete source credential","tags":["Sources"]}}}}
```


# Projects

APIs to organize maps

Projects help you organize maps and manage team permissions.

With these APIs, you can manage the projects in your workspace.

## List projects

> Retrieve all projects accessible to the authenticated user within the workspace.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Projects help you organize maps and manage team permissions.\n\nWith these APIs, you can manage the projects in your workspace.\n","name":"Projects"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"ProjectReferenceList":{"items":{"$ref":"#/components/schemas/ProjectReference"},"title":"ProjectReferenceList","type":"array"},"ProjectReference":{"additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/FeltID"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"max_inherited_permission":{"description":"The maximum permission level workspace members inherit on team-visible projects.","enum":["view_only","view_and_contribute","view_and_edit"],"nullable":false,"type":"string"},"name":{"type":"string"},"type":{"enum":["project_reference"],"type":"string"},"visibility":{"enum":["workspace","private"],"type":"string"}},"required":["id","type","name","visibility","max_inherited_permission"],"title":"ProjectReference","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/projects":{"get":{"callbacks":{},"description":"Retrieve all projects accessible to the authenticated user within the workspace.","operationId":"list_projects","parameters":[{"description":"Only needed when using the API as part of a plugin","in":"query","name":"workspace_id","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectReferenceList"}}},"description":"Projects"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"List projects","tags":["Projects"]}}}}
```

## Create project

> Create a new project with specified name and visibility settings within the workspace.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Projects help you organize maps and manage team permissions.\n\nWith these APIs, you can manage the projects in your workspace.\n","name":"Projects"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"ProjectCreateParams":{"additionalProperties":false,"properties":{"max_inherited_permission":{"description":"The maximum permission level workspace members inherit on team-visible projects. Only applicable when visibility is \"workspace\".","enum":["view_only","view_and_contribute","view_and_edit"],"nullable":false,"type":"string"},"name":{"description":"The name to be used for the Project","type":"string"},"visibility":{"description":"Either viewable by all members of the workspace, or private to users who are invited.","enum":["workspace","private"],"type":"string"}},"required":["name","visibility"],"title":"ProjectCreateParams","type":"object"},"Project":{"additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/FeltID"},"maps":{"items":{"$ref":"#/components/schemas/MapReference"},"type":"array"},"max_inherited_permission":{"description":"The maximum permission level workspace members inherit on team-visible projects.","enum":["view_only","view_and_contribute","view_and_edit"],"nullable":false,"type":"string"},"name":{"type":"string"},"type":{"enum":["project"],"type":"string"},"visibility":{"enum":["workspace","private"],"type":"string"}},"required":["id","type","name","visibility","max_inherited_permission","maps"],"title":"Project","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"MapReference":{"additionalProperties":false,"properties":{"created_at":{"format":"date_time","type":"string"},"folder_id":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"project_id":{"nullable":false,"type":"string"},"public_access":{"enum":["private","view_only","view_and_comment","view_comment_and_edit"],"type":"string"},"thumbnail_url":{"description":"A static thumbnail image of the map","nullable":true,"type":"string"},"title":{"type":"string"},"type":{"enum":["map_reference"],"type":"string"},"url":{"type":"string"},"visited_at":{"format":"date_time","nullable":true,"type":"string"}},"required":["id","type","url","title","thumbnail_url","created_at","visited_at","project_id","folder_id","public_access"],"title":"MapReference","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/projects":{"post":{"callbacks":{},"description":"Create a new project with specified name and visibility settings within the workspace.","operationId":"create_project","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectCreateParams"}}},"description":"Project create params","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}},"description":"Project"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Create project","tags":["Projects"]}}}}
```

## Get project

> Retrieve detailed information about a specific project including metadata, permissions, and references to the maps in the project.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Projects help you organize maps and manage team permissions.\n\nWith these APIs, you can manage the projects in your workspace.\n","name":"Projects"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"Project":{"additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/FeltID"},"maps":{"items":{"$ref":"#/components/schemas/MapReference"},"type":"array"},"max_inherited_permission":{"description":"The maximum permission level workspace members inherit on team-visible projects.","enum":["view_only","view_and_contribute","view_and_edit"],"nullable":false,"type":"string"},"name":{"type":"string"},"type":{"enum":["project"],"type":"string"},"visibility":{"enum":["workspace","private"],"type":"string"}},"required":["id","type","name","visibility","max_inherited_permission","maps"],"title":"Project","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"MapReference":{"additionalProperties":false,"properties":{"created_at":{"format":"date_time","type":"string"},"folder_id":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"project_id":{"nullable":false,"type":"string"},"public_access":{"enum":["private","view_only","view_and_comment","view_comment_and_edit"],"type":"string"},"thumbnail_url":{"description":"A static thumbnail image of the map","nullable":true,"type":"string"},"title":{"type":"string"},"type":{"enum":["map_reference"],"type":"string"},"url":{"type":"string"},"visited_at":{"format":"date_time","nullable":true,"type":"string"}},"required":["id","type","url","title","thumbnail_url","created_at","visited_at","project_id","folder_id","public_access"],"title":"MapReference","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/projects/{project_id}":{"get":{"callbacks":{},"description":"Retrieve detailed information about a specific project including metadata, permissions, and references to the maps in the project.","operationId":"show_project","parameters":[{"description":"","in":"path","name":"project_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}},"description":"Project"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Get project","tags":["Projects"]}}}}
```

## Delete project

> Permanently delete a project and all its contained maps and folders.\
> \
> {% hint style="danger" %}\
> Caution: Deleting a project deletes all of the folders and maps inside!\
> {% endhint %}<br>

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Projects help you organize maps and manage team permissions.\n\nWith these APIs, you can manage the projects in your workspace.\n","name":"Projects"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/projects/{project_id}":{"delete":{"callbacks":{},"description":"Permanently delete a project and all its contained maps and folders.\n\n{% hint style=\"danger\" %}\nCaution: Deleting a project deletes all of the folders and maps inside!\n{% endhint %}\n","operationId":"delete_project","parameters":[{"description":"The ID of the Project to delete. Note: This will delete all Folders and Maps inside the project!","in":"path","name":"project_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Delete project","tags":["Projects"]}}}}
```

## Update project

> Update project properties including name and visibility settings.

```json
{"openapi":"3.0.0","info":{"title":"Felt","version":"2.0"},"tags":[{"description":"Projects help you organize maps and manage team permissions.\n\nWith these APIs, you can manage the projects in your workspace.\n","name":"Projects"}],"servers":[{"url":"https://felt.com","variables":{}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"YOUR_API_KEY","scheme":"bearer","type":"http"}},"schemas":{"ProjectUpdateParams":{"additionalProperties":false,"properties":{"max_inherited_permission":{"description":"The maximum permission level workspace members inherit on team-visible projects. Only applicable when visibility is \"workspace\".","enum":["view_only","view_and_contribute","view_and_edit"],"nullable":false,"type":"string"},"name":{"description":"The name to be used for the Project","type":"string"},"visibility":{"description":"Either viewable by all members of the workspace, or private to users who are invited.","enum":["workspace","private"],"type":"string"}},"title":"ProjectUpdateParams","type":"object"},"Project":{"additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/FeltID"},"maps":{"items":{"$ref":"#/components/schemas/MapReference"},"type":"array"},"max_inherited_permission":{"description":"The maximum permission level workspace members inherit on team-visible projects.","enum":["view_only","view_and_contribute","view_and_edit"],"nullable":false,"type":"string"},"name":{"type":"string"},"type":{"enum":["project"],"type":"string"},"visibility":{"enum":["workspace","private"],"type":"string"}},"required":["id","type","name","visibility","max_inherited_permission","maps"],"title":"Project","type":"object"},"FeltID":{"format":"felt_id","nullable":false,"title":"FeltID","type":"string"},"MapReference":{"additionalProperties":false,"properties":{"created_at":{"format":"date_time","type":"string"},"folder_id":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/FeltID"},"links":{"properties":{"self":{"type":"string"}},"type":"object"},"project_id":{"nullable":false,"type":"string"},"public_access":{"enum":["private","view_only","view_and_comment","view_comment_and_edit"],"type":"string"},"thumbnail_url":{"description":"A static thumbnail image of the map","nullable":true,"type":"string"},"title":{"type":"string"},"type":{"enum":["map_reference"],"type":"string"},"url":{"type":"string"},"visited_at":{"format":"date_time","nullable":true,"type":"string"}},"required":["id","type","url","title","thumbnail_url","created_at","visited_at","project_id","folder_id","public_access"],"title":"MapReference","type":"object"},"UnauthorizedError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"header":{"enum":["authorization"],"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"UnauthorizedError","type":"object"},"NotFoundError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"NotFoundError","type":"object"},"JsonErrorResponse":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"pointer":{"type":"string"}},"required":["pointer"],"type":"object"},"title":{"type":"string"}},"required":["title","source","detail"],"type":"object"},"type":"array"}},"required":["errors"],"title":"JsonErrorResponse","type":"object"},"InternalServerError":{"properties":{"errors":{"items":{"properties":{"detail":{"type":"string"},"source":{"properties":{"parameter":{"type":"string"}},"type":"object"},"title":{"type":"string"}},"type":"object"},"type":"array"}},"title":"InternalServerError","type":"object"}}},"paths":{"/api/v2/projects/{project_id}/update":{"post":{"callbacks":{},"description":"Update project properties including name and visibility settings.","operationId":"update_project","parameters":[{"description":"The ID of the project to update","in":"path","name":"project_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectUpdateParams"}}},"description":"Project update params","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}},"description":"Project"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}},"description":"UnauthorizedError"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}},"description":"NotFoundError"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonErrorResponse"}}},"description":"Unprocessable Entity"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"}}},"description":"InternalServerError"}},"summary":"Update project","tags":["Projects"]}}}}
```


# Getting started

The Felt SDK allows you to control your Felt maps and build powerful, interactive custom applications. You can control many aspects of the Felt UI and map contents, as well as receive notifications of events happening in the map such as clicks, selections, and more.

This feature is available to customers on the [Enterprise plan](https://felt.com/pricing). Reach out to [set up a trial](https://felt.com/sales).

See our [examples](/js-sdk/examples) page to explore what you can build with the SDK.

There are two main ways to use the Felt SDK:

1. Extensions
2. Embedded maps

### Extensions

Write code directly within Felt using our [Extensions](https://help.felt.com/dashboards-and-apps/extensions) feature. Extensions run directly within the Felt environment, giving you immediate access to all SDK functionality without embedding or connection steps.

<figure><img src="/files/q5uiHnO9AmvxTq19femN" alt=""><figcaption></figcaption></figure>

When creating an extension, you automatically have access to a [`FeltController`](https://developers.felt.com/js-sdk-api-reference/main/feltcontroller) object named `felt` with no setup required. This controller provides all the methods you need to interact with your Felt map, including `getViewport`, `createElement`, `setLayerStyle`, and many more.

```javascript
// In a Felt extension, the controller is automatically available
const layers = await felt.getLayers();

// Listen for map events
felt.onSelectionChange({
  handler: ({ selection }) => console.log("Selection changed:", selection),
});
```

### Embedded maps

Embed Felt maps in your own applications and control them remotely.

#### What you'll need

* A Felt map to embed. Open any map you have access to and grab its ID from the URL: in `felt.com/map/Readable-Title-xPV9BqMuYQxmUraVWy9C89BNA`, the ID is the trailing part, `xPV9BqMuYQxmUraVWy9C89BNA`.
* The map's sharing settings must allow the visitor to view it. Public and unlisted maps work out of the box; for private maps see [Embed options](/js-sdk/embed-options#embedding-private-maps).

#### Installation

Install the SDK using your preferred package manager:

```bash
npm install @feltmaps/js-sdk
```

Alternatively, load it straight from a CDN in a `<script type="module">` — no build step required:

```javascript
import { Felt } from "https://esm.run/@feltmaps/js-sdk";
```

#### Embed your first map

Create an HTML page with a container element. Give the container an explicit height — an iframe inside a zero-height container renders as an invisible sliver, which is the most common first-run problem:

```html
<html>
  <head>
    <style>
      #container { height: 500px; }
    </style>
  </head>
  <body>
    <div id="container"></div>
    <script type="module" src="main.js"></script>
  </body>
</html>
```

Embed a Felt map in your container element and use the SDK to read from it:

```javascript
// main.js
import { Felt } from "@feltmaps/js-sdk";

const felt = await Felt.embed(
  document.querySelector("#container"),
  "FELT_MAP_ID", // Replace with your map's ID
);

const layers = await felt.getLayers();
console.log(`This map has ${layers.length} layers`);
```

**You should see your map load inside the container**, with the Felt legend and zoom controls visible. Open the browser console and you should see the layer count logged. If the container stays empty, check that the map ID is correct and that the map's sharing settings allow viewing.

Throughout these docs, code examples assume a controller variable named `felt`, whether it came from `Felt.embed` or from the extension environment.

#### Next steps

* [General concepts](/js-sdk/general-concepts) — the mental model: controllers, promises, listeners, and what persists.
* [Controlling maps](/js-sdk/controlling-maps) — viewports, and connecting to existing iframes.
* [Embed options](/js-sdk/embed-options) — UI controls, initial viewport, private maps.
* [Integrating with React](/js-sdk/integrating-with-react) — React hooks for the SDK, and the [React starter repo](https://github.com/felt/js-sdk-starter-react) for a quick start.


# General concepts

This guide covers the mental model and the common patterns used throughout the Felt SDK. Understanding these once makes every other page predictable.

## The Felt controller

Everything you do with the SDK goes through a **controller** object — named `felt` in all the examples in these docs. How you get it depends on where your code runs:

* **Embeds** — your code runs in *your* page, and the map lives in an iframe. `Felt.embed(container, mapId, options)` creates the iframe and returns a controller for it. You can also use `Felt.connect(iframe.contentWindow)` to attach a controller to a Felt iframe you've already placed on the page. See [Controlling maps](/js-sdk/controlling-maps) and [Embed options](/js-sdk/embed-options).
* **Extensions** — your code runs *inside* the Felt app, and receives a ready-made controller: no embedding required. See the [extensions documentation](https://help.felt.com/dashboards-and-apps/extensions).

The controller communicates with the map via message passing, which is why every read is asynchronous (see promises below). The same controller API is available in both contexts.

```typescript
import { Felt } from "@feltmaps/js-sdk";

const felt = await Felt.embed(
  document.getElementById("container"),
  FELT_MAP_ID,
);
```

## Session-only vs persisted changes

A crucial rule for building on the SDK: **changes made via the SDK are visible only to the current session — they are not saved to the map.** This applies to:

* Annotations created with [`createElement`](/js-sdk/drawing-annotations)
* Layers created with [`createLayersFromGeoJson`](/js-sdk/working-with-layers)
* Style changes made with `setLayerStyle`
* Filters set with [`setLayerFilters`](/js-sdk/layer-filters)

This makes the SDK safe for building per-visitor experiences on shared maps: ten visitors can each see their own filters and drawings without affecting each other or the underlying map. To make persistent changes to a map, use the [REST API](/rest-api/getting-started).

## Use of promises

All methods in the Felt SDK are asynchronous and return Promises — writes as well as reads. This means you'll need to use `await` or `.then()` when calling them:

```typescript
const layer = await felt.getLayer("layer-1");
felt.getElements().then(elements => {
  console.log(elements);
});
```

## Getting entities

The SDK follows a consistent pattern for getting entities. For each entity type, there are usually two getters:

1. A singular getter for retrieving one entity by ID:

```typescript
const layer = await felt.getLayer("layer-1");
const element = await felt.getElement("element-1");
```

2. A plural getter that accepts constraints for retrieving multiple entities:

```typescript
const layers = await felt.getLayers({ ids: ["layer-1", "layer-2"] });
const legendItems = await felt.getLegendItems({ layerIds: ["layer-1", "layer-2"] });
```

The plural getters also allow you to pass no constraints, in which case they'll return all entities of that type:

```typescript
const layers = await felt.getLayers();
const legendItems = await felt.getLegendItems();
```

### Getters can return null

Singular getters return `null` when the entity doesn't exist, and plural getters can contain `null` entries — always check before using the result:

```typescript
const layer = await felt.getLayer("layer-1");
if (layer) {
  // Layer exists, safe to use
  console.log(layer.visible);
} else {
  console.log("Layer not found");
}
```

### Batch your reads

When you need multiple entities, use the plural methods with constraints rather than making multiple individual calls:

```typescript
// Better approach
const layers = await felt.getLayers({ ids: ["layer-1", "layer-2"] });

// Less efficient approach
const layer1 = await felt.getLayer("layer-1");
const layer2 = await felt.getLayer("layer-2");
```

## Change listeners

Each entity type has a corresponding change listener method following the pattern `on{EntityType}Change`:

```typescript
const unsubscribe = felt.onLayerChange({
  options: { id: "layer-1" },
  handler: ({ layer }) => {
    console.log("Layer updated:", layer);
  }
});
```

There are also various other setters and getters in the Felt SDK that follow this convention as much as possible. For example, selection:

```typescript
const selection = await felt.getSelection();
const unsubscribe = felt.onSelectionChange({
  handler: ({ selection }) => {
    console.log("Selection updated:", selection);
  }
});
```

And layer filters — note that this particular listener receives the filters value directly, with no wrapper object:

```typescript
const filters = await felt.getLayerFilters("layer-1");
await felt.setLayerFilters({
  layerId: "layer-1",
  filters: ["name", "eq", "Jane"],
});
const unsubscribe = felt.onLayerFiltersChange({
  options: { layerId: "layer-1" },
  handler: (filters) => console.log(filters.combined),
});
```

For the full list of listeners and their payloads, see the [Events reference](/js-sdk/events-reference).

### Cleanup functions

All change listeners return an unsubscribe function that should be called when you no longer need the listener:

```typescript
const unsubscribe = felt.onLayerChange({
  options: { id: "layer-1" },
  handler: ({ layer }) => {
    console.log("Layer changed:", layer);
  }
});

// Later, when you're done listening:
unsubscribe();
```

This is particularly important in frameworks like React where you should clean up listeners when components unmount:

```typescript
useEffect(() => {
  const unsubscribe = felt.onViewportMove({
    handler: (viewport) => {
      console.log("Viewport changed:", viewport);
    }
  });

  // Clean up when the component unmounts
  return () => unsubscribe();
}, []);
```

### Handler and options structure

Change listeners always take a single object parameter containing both `options` and `handler`. This structure makes it easier to add new options in the future without breaking existing code:

```typescript
// Current API
felt.onElementChange({
  options: { id: "element-1" },
  handler: ({ element }) => { /* ... */ }
});

// If we need to add new options later, no breaking changes:
felt.onElementChange({
  options: { 
    id: "element-1",
    newOption: "value" // Can add new options without breaking existing code
  },
  handler: ({ element }) => { /* ... */ }
});
```

## Entity nodes

When dealing with mixed collections of entities (like in selection events), each entity is wrapped in an `EntityNode` object that includes type information:

```typescript
felt.onSelectionChange({
  handler: ({ selection }) => {
    selection.forEach(node => {
      console.log(node.type);    // e.g., "element", "layer", "feature", ...
      console.log(node.entity);  // The actual entity object
      
      if (node.type === "element") {
        // TypeScript knows this is an Element
        console.log(node.entity.attributes);
      }
    });
  }
});
```


# Controlling maps

The Felt SDK has a number of methods for interacting with maps, depending on how you set up your HTML.

All Felt maps are embedded in iframes, and the SDK can do this for you or can connect to an existing Felt iframe.

### Felt map IDs

Felt map IDs are unique identifiers for Felt maps. They are used to embed maps in iframes, and to connect to existing iframes.

To get the ID of a Felt map, click the Map settings button in the main toolbar, and then you can see the Map ID in the Developers section.

<figure><img src="/files/YCSqGDYYUiTLpIErQIXn" alt=""><figcaption></figcaption></figure>

Alternatively, you can look at the URL of the map. For example, the map at `https://felt.com/map/Map-title-xPV9BqMuYQxmUraVWy9C89BNA` has the ID `xPV9BqMuYQxmUraVWy9C89BNA`.

Throughout the documentation, we'll use the placeholder `FELT_MAP_ID` to refer to a Felt map ID.

### Using `Felt.embed` to create an iframe

Create an HTML page with a container element:

```html
<html>
  <body>
    <h1>My Felt app</h1>
    <div id="container"></div>
  </body>
</html>
```

Embed a Felt map in your container element and use the SDK to control it by calling `Felt.embed`, passing the container element as the first argument:

```javascript
import { Felt } from "@feltmaps/js-sdk";

const felt = await Felt.embed(
  document.querySelector("#container"),
  "FELT_MAP_ID",
);

// Now use the SDK
const layers = await felt.getLayers();
const elements = await felt.getElements();

// You also have a reference to the iframe itself:
felt.iframe.style.width = "50%";
```

`Felt.embed` also takes a third argument with options controlling the embed's UI, initial viewport, and authentication — see [Embed options](/js-sdk/embed-options).

### Using `Felt.embed` to mount into an existing iframe

In some cases, you may want to add a "template" iframe to your page. This can be useful if you want to style your iframe in a specific way, or if you already have one map embedded and want to mount and control a different map.

In this case, you can call `Felt.embed` with the iframe element as the first argument:

```html
<html>
  <body>
    <h1>My Felt app</h1>
    <iframe id="my-iframe"></iframe>
  </body>
</html>
```

```javascript
import { Felt } from "@feltmaps/js-sdk";

const felt = await Felt.embed(
  document.querySelector("#my-iframe"),
  "FELT_MAP_ID",
);
```

### Using `Felt.connect` to connect to an existing embedded Felt map

There may be cases where you already have a Felt map embedded in an iframe, and you want to control it using the SDK. This can be useful if your HTML is server-rendered with the Felt map already embedded.

In this case, you can call `Felt.connect` with the iframe's window as the first argument:

```html
<html>
  <body>
    <h1>My Felt app</h1>
    <iframe src="https://felt.com/map/Map-title-xPV9BqMuYQxmUraVWy9C89BNA" id="my-iframe"></iframe>
  </body>
</html>
```

```javascript
import { Felt } from "@feltmaps/js-sdk";

const felt = await Felt.connect(
  document.querySelector("#my-iframe").contentWindow
);
```

Note that in this case, you don't need to pass the Felt map ID to `Felt.connect`, because we are connecting to a map that has already been embedded.


# Embed options

`Felt.embed()` takes an optional third argument that controls how the embedded map looks and behaves:

```typescript
const felt = await Felt.embed(container, FELT_MAP_ID, {
  uiControls: {
    showLegend: false,
    cooperativeGestures: false,
  },
  initialViewport: {
    center: { latitude: 40.7128, longitude: -74.006 },
    zoom: 10,
  },
});
```

## UI controls

All `uiControls` options are booleans:

| Option                | Default | Description                                                                                                                                                                                                                           |
| --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `showLegend`          | `true`  | Whether the legend is shown.                                                                                                                                                                                                          |
| `cooperativeGestures` | `true`  | Adjusted gesture behavior for embeds: on mobile, one-finger drag scrolls the page while two fingers pan the map; on desktop, scroll-to-zoom requires holding Ctrl/Cmd. Disable for app-like embeds where the map is the main content. |
| `fullScreenButton`    | `true`  | Shows a button that opens the map in a new tab or window.                                                                                                                                                                             |
| `geolocation`         | `false` | Shows a geolocation button that plots and tracks the visitor's position.                                                                                                                                                              |
| `zoomControls`        | `true`  | Shows the zoom controls (bottom right). Hiding them does not prevent zooming.                                                                                                                                                         |
| `scaleBar`            | `true`  | Shows the scale bar.                                                                                                                                                                                                                  |

You can change these after the map has loaded with [`updateUiControls`](https://developers.felt.com/js-sdk-api-reference/ui/uicontroller#updateuicontrols):

```typescript
felt.updateUiControls({ showLegend: false });
```

## Initial viewport

`initialViewport` overrides the map's saved viewport with a center and zoom for this embed:

```typescript
initialViewport: {
  center: { latitude: 40.7128, longitude: -74.006 },
  zoom: 10,
}
```

## Embedding private maps

Public and unlisted maps embed with no extra configuration. To embed a **private** map for visitors who aren't logged into Felt, generate a short-lived embed token server-side with the REST API and pass it as the `token` option:

1. Your server calls [`POST /api/v2/maps/{map_id}/embed_token?user_email=…`](https://felt.com/api/v2/openapi.json), using your [API token](/rest-api/authentication). The visitor's `user_email` is a required **query parameter**, not a body field, and that address should belong to a member of your workspace. The response contains a token valid for 15 minutes.
2. Your page passes that token to `Felt.embed`:

```typescript
const felt = await Felt.embed(container, FELT_MAP_ID, {
  token: embedTokenFromYourServer,
});
```

Never call the embed token endpoint from the browser — that would expose your API token. Generate embed tokens on your server and hand only the short-lived token to the client.


# Integrating with React

To work with Felt embeds in React, we have a starter template that you can use as a starting point.

This is available on GitHub in the [felt/js-sdk-starter-react](https://github.com/felt/js-sdk-starter-react) repository.

In that repo, you will find a `feltUtils.ts` file that demonstrates some ways to make using the Felt SDK in React easier. The SDK itself is framework-agnostic and ships no React bindings — the hooks below are small wrappers you copy into your own project.

### Embedding with `useFeltEmbed`

```tsx
function MyComponent() {
  // get the felt controller (or null if it's not loaded yet) and a ref to the map container
  // into which we can embed the map
  const { felt, mapRef } = useFeltEmbed("FELT_MAP_ID", {
    uiControls: {
      cooperativeGestures: false,
      fullScreenButton: false,
      showLegend: false,
    },
  });

  return (
    <div>
      {/* the map container — remember to give it a height in your CSS */}
      <div ref={mapRef} />

      {/* a component that uses the Felt controller */}
      <MyFeltApp felt={felt} />
    </div>
  );
}
```

<details>

<summary>useFeltEmbed implementation</summary>

```typescript
import {
  Felt,
  FeltController,
  FeltEmbedOptions,
  Layer,
} from "@feltmaps/js-sdk";
import React from "react";

export function useFeltEmbed(mapId: string, embedOptions: FeltEmbedOptions) {
  const [felt, setFelt] = React.useState<FeltController | null>(null);
  const hasLoadedRef = React.useRef(false);
  const mapRef = React.useRef<HTMLDivElement>(null);

  React.useEffect(() => {
    async function loadFelt() {
      if (hasLoadedRef.current) return;
      if (!mapRef.current) return;

      hasLoadedRef.current = true;
      const felt = await Felt.embed(mapRef.current, mapId, embedOptions);
      setFelt(felt);
    }

    loadFelt();
  }, []);

  return {
    felt,
    mapRef,
  };
}
```

A few things worth knowing about this hook:

* The `hasLoadedRef` guard exists because React's Strict Mode runs effects twice in development — without it you would embed two iframes.
* The hook embeds once for the component's lifetime: changing `mapId` after mount won't re-embed. If you need to switch maps, remount the component (for example with a `key={mapId}` prop).

</details>

### Getting live data

A common use case for building apps on Felt is to be notified when entities are updated. The main example of this is when you want to change the visibility of say a layer, and have your own UI reflect that change.

Rather than keeping track of the visibility of entities yourself, you can use the Felt SDK to listen for changes to the visibility of entities.

Here is an example of how you might do this for layers assuming you already have a reference to a `Layer` object, e.g. from calling `felt.getLayers()`:

```typescript
export function useLiveLayer(felt: FeltController, initialLayer: Layer) {
  // start with the layer we were given
  const [currentLayer, setLayer] = React.useState<Layer | null>(initialLayer);

  // listen for changes to the layer and update our state accordingly
  // (onLayerChange returns its unsubscribe function, so returning it
  // from the effect cleans up the listener on unmount)
  React.useEffect(() => {
    return felt.onLayerChange({
      options: { id: initialLayer.id },
      handler: ({ layer }) => setLayer(layer),
    });
  }, [felt, initialLayer.id]);

  // return the live layer
  return currentLayer;
}
```


# Map interactions and viewport

The Felt SDK provides methods to control the map's viewport (the visible area of the map) and handle user interactions like clicking and hovering on the viewport.

## Working with the viewport

### Getting viewport state

You can get the current viewport state using `getViewport()`:

```typescript
const viewport = await felt.getViewport();
console.log(viewport.center); // { latitude: number, longitude: number }
console.log(viewport.zoom);   // number
```

### Setting the viewport

There are two main ways to set the viewport: moving to a specific point, or fitting to bounds.

#### Moving to a point

Use `setViewport()` to move the map to a specific location:

```typescript
felt.setViewport({
  center: {
    latitude: 37.7749,
    longitude: -122.4194
  },
  zoom: 12
});
```

#### Fitting to bounds

Use `fitViewportToBounds()` to adjust the viewport to show a specific rectangular area:

```typescript
felt.fitViewportToBounds({
  bounds: [
    west,   // minimum longitude
    south,  // minimum latitude
    east,   // maximum longitude
    north   // maximum latitude
  ]
});
```

### Responding to viewport changes

To stay in sync with viewport changes, use the `onViewportMove` method:

```typescript
const unsubscribe = felt.onViewportMove({
  handler: (viewport) => {
    console.log("New center:", viewport.center);
    console.log("New zoom:", viewport.zoom);
  }
});

// Clean up when done
unsubscribe();
```

## Map interactions

### Click events

Listen for click events on the map using `onPointerClick`:

```typescript
const unsubscribe = felt.onPointerClick({
  handler: (event) => {
    // Location of the click
    console.log("Click location:", event.coordinate);
    
    // Features under the click
    console.log("Clicked features:", event.features);
    
    // The pixel coordinates of the cursor, measured from the top left corner of the map DOM element.
    console.log("Screen coordinates:", event.point);
    
    // Values from raster layers under the pointer.
    // Always an array - empty when no raster layers are hit.
    for (const {value, categoryName, color, layerId} of event.rasterValues) {
      console.log("Raster value:", value, categoryName);
    }
  }
});
```

### Hover events

Track mouse movement over the map using `onPointerMove`:

```typescript
const unsubscribe = felt.onPointerMove({
  handler: (event) => {
    // Current mouse location
    console.log("Mouse location:", event.coordinate);
    
    // Features under the cursor
    console.log("Hovered features:", event.features);
    
    // The pixel coordinates of the cursor, measured from the top left corner of the map DOM element.
    console.log("Screen coordinates:", event.point);

    // Values from raster layers under the pointer.
    // Always an array - empty when no raster layers are hit.
    for (const {value, categoryName, color, layerId} of event.rasterValues) {
      console.log("Raster value:", value, categoryName);
    }
  }
});
```

## Best practices

1. **Cleanup**: Always store and call unsubscribe functions when you're done listening for events:

```typescript
const unsubscribe = felt.onPointerMove({
  handler: (event) => {
    // Handle event...
  }
});

// Later, when you're done:
unsubscribe();
```

2. **Throttling**: For pointer move events, consider throttling your handler if you're doing expensive operations:

```typescript
import { throttle } from "lodash";

felt.onPointerMove({
  handler: throttle((event) => {
    // Handle frequent mouse moves...
  }, 100) // Limit to once every 100ms
});
```

By using these viewport controls and interaction handlers, you can create rich, interactive experiences with your Felt map.


# Working with selection

The Felt SDK provides functionality for reading the current selection state and selecting features on the map programmatically. This is useful for building interactive experiences that respond to data analysis or user interactions.

## Selecting features

Features are individual data points within layers. You can select features programmatically using the [`selectFeature()`](https://developers.felt.com/js-sdk-api-reference/main/feltcontroller#selectfeature) method. **Only one feature can be selected at a time** - selecting a new feature will replace the current selection.

```typescript
await felt.selectFeature({
  id: "feature-123",
  layerId: "buildings-layer"
});
```

The [`selectFeature()`](https://developers.felt.com/js-sdk-api-reference/main/feltcontroller#selectfeature) method accepts options to control the selection behavior:

```typescript
await felt.selectFeature({
  id: "feature-123",
  layerId: "buildings-layer",
  showPopup: false,           // Whether to show the feature popup (default: true)
  fitViewport: { maxZoom: 15 } // Fit viewport to feature with zoom limit
});
```

* `showPopup` (boolean, default: `true`) - Whether to display the feature information popup
* `fitViewport` (boolean | `{ maxZoom: number }`, default: `true`) - Whether to fit the viewport to the feature. Can be `true`, `false`, or an object with `maxZoom` to limit the zoom level used.

## Reading selection

You can get the current selection state using [`getSelection()`](https://developers.felt.com/js-sdk-api-reference/main/feltcontroller#getselection). This returns an array of [`EntityNode`](https://developers.felt.com/js-sdk-api-reference/selection/entitynode) objects, each representing a selected entity. The selection can include various types of entities at the same time, such as annotations and features:

```typescript
const selection = await felt.getSelection();

console.log(`${selection.length} items selected`);

selection.forEach(node => {
  switch(node.type) {
    case 'feature':
      console.log('Selected feature:', node.entity.id, 'from layer:', node.entity.layerId);
      break;
    case 'element':
      console.log('Selected element:', node.entity.name || node.entity.type);
      break;
  }
});
```

## Clearing selection

Remove current selections using [`clearSelection()`](https://developers.felt.com/js-sdk-api-reference/main/feltcontroller#clearselection):

```typescript
// Clear all selections
await felt.clearSelection();

// Clear specific types of selections
await felt.clearSelection({ 
  features: true,   // Clear feature selections
  elements: false   // Keep element selections
});
```

## Reacting to selection changes

To stay in sync with selection changes, use the [`onSelectionChange()`](https://developers.felt.com/js-sdk-api-reference/main/feltcontroller#onselectionchange) method:

```typescript
const unsubscribe = felt.onSelectionChange({
  handler: ({ selection }) => {
    // selection is an array of EntityNode objects
    console.log("Selected entities:", selection);
    
    // Check what's selected
    selection.forEach(node => {
      console.log("Entity type:", node.type);
      console.log("Entity ID:", node.entity.id);
    });
  }
});

// Don't forget to clean up when you're done
unsubscribe();
```

## Best practices

1. **Clean up listeners**: Always store and call the unsubscribe function when you no longer need to listen for selection changes:

```typescript
const unsubscribe = felt.onSelectionChange({
  handler: ({ selection }) => {
    // Handle selection...
  }
});

// Later, when you're done:
unsubscribe();
```

2. **Handle empty selection**: Remember that the selection array might be empty if nothing is selected:

```typescript
const unsubscribe = felt.onSelectionChange({
  handler: ({ selection }) => {
    if (selection.length === 0) {
      console.log("Nothing is selected");
      return;
    }
    // Handle selection...
  }
});
```

By following these patterns, you can build robust interactions based on what users select in your Felt map.


# Hiding and showing

The Felt SDK provides methods to control the visibility of various entities like layers, layer groups, annotation groups, and legend items. These methods are designed to efficiently handle bulk operations.

## Understanding visibility requests

All visibility methods use a consistent structure that allows both showing and hiding entities in a single call:

```typescript
{
  show?: string[],  // IDs of entities to show
  hide?: string[]   // IDs of entities to hide
}
```

### Layers

Control visibility of layers using `setLayerVisibility`:

```typescript
felt.setLayerVisibility({
  show: ["layer-1", "layer-2"],
  hide: ["layer-3"]
});
```

### Layer groups

Control visibility of layer groups using `setLayerGroupVisibility`:

```typescript
felt.setLayerGroupVisibility({
  show: ["group-1", "group-2"],
  hide: ["group-3"]
});
```

### Annotation groups

Similarly, control annotation group visibility with `setElementGroupVisibility`:

```typescript
felt.setElementGroupVisibility({
  show: ["points-group"],
  hide: ["lines-group", "polygons-group"]
});
```

### Legend items

Legend items require both a layer ID and an item ID to identify them. Use `setLegendItemVisibility`:

```typescript
felt.setLegendItemVisibility({
  show: [
    { layerId: "layer-1", id: "item-1" },
    { layerId: "layer-1", id: "item-2" }
  ],
  hide: [
    { layerId: "layer-1", id: "item-3" }
  ]
});
```

## Common use cases

### Focusing on a single layer

To focus on a single layer by hiding all others, first get all layers and then use their IDs:

```typescript
const layers = await felt.getLayers();
const targetLayerId = "important-layer";

felt.setLayerVisibility({
  show: [targetLayerId],
  hide: layers
    .map(layer => layer?.id)
    .filter(id => id && id !== targetLayerId)
});
```

### Toggling visibility

When implementing a toggle, you can use empty arrays for the operation you don't need:

```typescript
function toggleLayer(layerId: string, visible: boolean) {
  felt.setLayerVisibility({
    show: visible ? [layerId] : [],
    hide: visible ? [] : [layerId]
  });
}
```

## Best practices

1. **Batch operations**: Use a single call with multiple IDs rather than making multiple calls:

```typescript
// Better approach
felt.setLayerVisibility({
  show: ["layer-1", "layer-2"],
  hide: ["layer-3", "layer-4"]
});

// Less efficient approach
felt.setLayerVisibility({ show: ["layer-1"] });
felt.setLayerVisibility({ show: ["layer-2"] });
felt.setLayerVisibility({ hide: ["layer-3"] });
felt.setLayerVisibility({ hide: ["layer-4"] });
```

2. **Omit unused properties**: When you only need to show or hide, omit the unused property rather than including it with an empty array:

```typescript
// Do this
felt.setLayerVisibility({
  show: ["layer-1"]
});
```


# Working with layers

The Felt SDK allows you to add GeoJSON data to your maps from various sources:

* Remote URLs
* Local files
* Programmatically generated GeoJSON data

GeoJSON layers created via the SDK are temporary and session-specific - they're not permanently added to the map and won't be visible to other users.

When creating a GeoJSON layer, you can specify different styles for each geometry type (Point, Line, Polygon) that might be found in the source. Each geometry type will create its own layer. It's important to note that GeoJSON layers added via the SDK have limited capabilities compared to regular Felt layers - they cannot be filtered, nor can statistics be fetched for them.

## Creating GeoJSON layers

Use the [`createLayersFromGeoJson`](https://developers.felt.com/js-sdk-api-reference/main/feltcontroller#createlayersfromgeojson) method to add GeoJSON layers to your map. This method accepts different source types depending on where your GeoJSON data comes from.

### From a URL

To create a layer from a GeoJSON file at a remote URL:

```javascript
const layerResult = await felt.createLayersFromGeoJson({
  source: {
    type: "geoJsonUrl",
    url: "https://example.com/data/neighborhoods.geojson",
    // Optional: Auto-refresh every 30 seconds
    refreshInterval: 30000
  },
  name: "Neighborhoods",
  caption: "Neighborhood boundaries for the city", // Optional
  description: "This layer shows the official neighborhood boundaries" // Optional
});

if (layerResult) {
  console.log("Created layer group:", layerResult.layerGroup);
  console.log("Created layers:", layerResult.layers);
}
```

### From a local file

To create a layer from a GeoJSON file on the user's device:

```javascript
// Assuming you have a File object from a file input
const fileInput = document.getElementById('geojson-upload');
const file = fileInput.files[0];

const layerResult = await felt.createLayersFromGeoJson({
  source: {
    type: "geoJsonFile",
    file: file
  },
  name: "User Uploaded Data"
});

if (layerResult) {
  // Store the layer ID for later reference
  const layerId = layerResult.layers[0].id;
}
```

### From GeoJSON data

To create a layer from GeoJSON data that you've generated or processed in your application. This approach is useful when you need to dynamically generate GeoJSON data based on user interactions or other app states:

```javascript
const geojsonData = {
  type: "FeatureCollection",
  features: [
    {
      type: "Feature",
      geometry: {
        type: "Point",
        coordinates: [-122.4194, 37.7749]
      },
      properties: {
        name: "San Francisco",
        population: 874961
      }
    },
    // Additional features...
  ]
};

const layerResult = await felt.createLayersFromGeoJson({
  source: {
    type: "geoJsonData",
    data: geojsonData
  },
  name: "Dynamic Points"
});
```

## Styling by geometry type

When creating GeoJSON layers, you can specify different styles for each geometry type that might be found in your data. The SDK will create separate layers for each geometry type:

```javascript
const layerResult = await felt.createLayersFromGeoJson({
  name: "Styled Features",
  source: {
    type: "geoJsonUrl",
    url: "https://example.com/data/mixed-features.geojson"
  },
  geometryStyles: {
    Point: {
      paint: { 
        color: "red", 
        size: 8 
      }
    },
    Line: {
      paint: { 
        color: "blue", 
        size: 4 
      },
      config: { 
        labelAttribute: ["name"] 
      },
      label: { 
        minZoom: 0 
      }
    },
    Polygon: {
      paint: { 
        color: "green", 
        strokeColor: "darkgreen",
        fillOpacity: 0.5
      }
    }
  }
});
```

Each style should be a valid [FSL](/felt-style-language/getting-started) (Felt Style Language) style. If you don't specify styles, Felt will apply default styles based on the geometry type.

## Deleting layers

To remove a GeoJSON layer:

```javascript
await felt.deleteLayer("layer-1");
```

Note that this only works for layers created via the SDK's `createLayersFromGeoJson` method, not for layers added through the Felt UI.

## Refreshing GeoJSON layers

For GeoJSON layers created from URLs, you can set automatic refreshing:

**At Creation Time**: By setting the `refreshInterval` parameter when creating the layer. The `refreshInterval` parameter is optional and specifies how frequently (in milliseconds) the layer should be automatically refreshed from the URL. Valid values range from 250ms to 5 minutes (300,000ms). If set to `null` or omitted, the layer won't refresh automatically.

```javascript
const layerResult = await felt.createLayersFromGeoJson({
  source: {
    type: "geoJsonUrl",
    url: "https://example.com/data/realtime-sensors.geojson",
    refreshInterval: 60000  // Refresh every minute
  },
  name: "Live Sensor Data"
});
```

**Manual refresh**: Replace the `source` property of any layer you have created, using the [`updateLayer`](https://developers.felt.com/js-sdk-api-reference/layers/layerscontroller#updatelayer) method to update the source data:

```javascript
await felt.updateLayer({
  id: layer.id,
  source: {
    type: "geoJsonData",
    data: updatedFeatureCollection,
  },
});
```


# Layer filters

The Felt SDK allows you to filter which features are visible in a layer using expressions that evaluate against feature properties. Filters can come from different sources and are combined to determine what's visible.

Note that filters only work on layers that have been uploaded to and processed by Felt — layers created at runtime with [`createLayersFromGeoJson`](/js-sdk/working-with-layers) cannot be filtered.

By understanding how filters work and combine, you can create dynamic views of your data that respond to user interactions and application state.

## Understanding filter sources

Layer filters can come from multiple sources, which are combined to create the final filter:

1. **Style filters**: Set by the map creator in the Felt UI
2. **Component filters**: Set through interactive legend components
3. **Ephemeral filters**: Set temporarily through the SDK

You can inspect these different filter sources using `getLayerFilters`:

```typescript
const filters = await felt.getLayerFilters("layer-1");
console.log(filters.style);      // Base filters from the layer style
console.log(filters.components); // Filters from legend components
console.log(filters.ephemeral);  // Filters set through the SDK
console.log(filters.combined);   // The final result of combining all filters
```

## Setting filters

Use `setLayerFilters` to apply ephemeral filters to a layer:

```typescript
felt.setLayerFilters({
  layerId: "layer-1",
  filters: ["POPULATION", "gt", 1000000]
});
```

This replaces any ephemeral filters currently set on the layer. You can also pass an optional `note` — a message shown on the layer legend while the filter is applied, along with a reset button that lets the user clear it:

```typescript
felt.setLayerFilters({
  layerId: "layer-1",
  filters: ["POPULATION", "gt", 1000000],
  note: "Showing cities with over 1M residents",
});
```

### Filter operators

The following operators are available:

* Comparison: `lt` (less than), `gt` (greater than), `le` (less than or equal), `ge` (greater than or equal), `eq` (equal), `ne` (not equal)
* Text: `cn` (contains), `nc` (does not contain)
* Boolean: `and`, `or`
* Lookup: `in` (contained in list), `ni` (not contained in list)
* Null checks: `is` / `isnt` (match against `null`)

See [the filters block](/felt-style-language/style-definition-blocks/the-filters-block) for more details on filter operators.

### Compound filters

You can combine multiple conditions using boolean operators:

```typescript
felt.setLayerFilters({
  layerId: "layer-1",
  filters: [
    ["POPULATION", "gt", 1000000],
    "and",
    ["COUNTRY", "eq", "USA"]
  ]
});
```

## Practical example: Filtering by selected feature

Here's a common use case where we filter a layer to show only features that match a property of a selected feature:

```typescript
// Listen for selection changes
felt.onSelectionChange({
  handler: async ({ selection }) => {
    // Find the first selected feature
    const selectedFeature = selection.find(node => node.type === "feature");
    
    if (selectedFeature) {
      // Get the state code from the selected feature
      const stateCode = selectedFeature.entity.properties.STATE_CODE;
      
      // Filter the counties layer to show only counties in the selected state
      felt.setLayerFilters({
        layerId: "counties-layer",
        filters: ["STATE_CODE", "eq", stateCode]
      });
    } else {
      // Clear the filter when nothing is selected
      felt.setLayerFilters({
        layerId: "counties-layer",
        filters: null
      });
    }
  }
});
```

## Best practices

1. **Clear filters**: Set filters to `null` to remove them entirely:

```typescript
felt.setLayerFilters({
  layerId: "layer-1",
  filters: null
});
```

2. **Check existing filters**: Remember that your ephemeral filters combine with existing style and component filters:

```typescript
const filters = await felt.getLayerFilters("layer-1");

// Check if there are any style filters before adding ephemeral ones
if (filters.style) {
  console.log("This layer already has style filters");
}
```

3. **Type safety**: Use TypeScript to ensure your filter expressions are valid:

```typescript
import type { Filters } from "@feltmaps/js-sdk";

const filter: Filters = ["POPULATION", "gt", 1000000];
felt.setLayerFilters({
  layerId: "layer-1",
  filters: filter
});
```


# Building custom charts

The Felt SDK provides powerful methods to analyze your geospatial data and transform it into informative visualizations. You can calculate statistics on entire datasets or focus on specific areas using boundaries and filters, allowing you to create custom charts that reveal insights about your spatial data.

Statistics are available for layers that have been uploaded to and processed by Felt — layers created at runtime with [`createLayersFromGeoJson`](/js-sdk/working-with-layers) cannot be queried for statistics.

## Data analysis methods

The SDK offers three complementary approaches to analyze your map data:

### 1. Aggregates: single statistics

Calculate individual values (count, sum, average, etc.) across your dataset or a filtered subset. If no aggregation method is provided, the count is returned.

```javascript
// Count all residential buildings
const residentialCount = await felt.getAggregates({
    layerId: "buildings",
    filters: ["type", "eq", "residential"]
});
// returns { count: 427 }

// Calculate average home value in a specific neighborhood
const avgHomeValue = await felt.getAggregates({
    layerId: "buildings",
    boundary: [-122.43, 47.60, -122.33, 47.62], // neighborhood boundary
    aggregation: {
        method: "avg",
        attribute: "assessed_value"
    }
});
// returns { avg: 652850.32 }
```

### 2. Categories: group by values

Group features by unique attribute values and calculate statistics for each group.

```javascript
// Basic grouping: Count of buildings by type
const buildingsByType = await felt.getCategoryData({
    layerId: "buildings",
    attribute: "type"
});
/* returns:
[
  { value: "residential", count: 427 },
  { value: "commercial", count: 82 },
  { value: "mixed-use", count: 38 },
  { value: "industrial", count: 15 }
]
*/
```

### 3. Histograms: group by numeric ranges

Create bins for numeric data and calculate statistics for each range.

```javascript
// Basic histogram: Building heights in 5 natural break bins
const buildingHeights = await felt.getHistogramData({
    layerId: "buildings",
    attribute: "height",
    steps: { type: "jenks", count: 5 }
});
/* returns:
[
  { min: 0, max: 20, count: 175 },
  { min: 20, max: 50, count: 203 },
  { min: 50, max: 100, count: 142 },
  { min: 100, max: 200, count: 36 },
  { min: 200, max: 500, count: 6 }
]
*/
```

## Working with filters

You can apply filters in two powerful ways:

1. **At the top level** - Affects both which data is included and how values are calculated
2. **In the values configuration** - Only affects the calculated values while keeping all categories/bins

This two-level filtering is especially useful for creating comparative visualizations while maintaining consistent groupings.

### Advanced filtering examples

**Comparing building types by floor area (Categories)**

```javascript
// Advanced: Show all building types, but only sum floor area of recent buildings
const recentBuildingAreaByType = await felt.getCategoryData({
    layerId: "buildings",
    attribute: "type",
    values: {
        filters: ["year_built", "ge", 2000],
        aggregation: {
            method: "sum",
            attribute: "floor_area"
        }
    }
});
/* returns:
[
  { value: "residential", sum: 1250000 },
  { value: "commercial", sum: 750000 },
  { value: "mixed-use", sum: 350000 },
  { value: "industrial", sum: 120000 }
]
*/
```

**Comparing building heights across time periods (Histograms)**

```javascript
// Compare old vs new buildings using the same height ranges
const oldBuildingHeights = await felt.getHistogramData({
    layerId: "buildings",
    attribute: "height",
    steps: [0, 20, 50, 100, 200, 500],
    values: {
        filters: ["year_built", "lt", 1950]
    }
});
/* returns:
[
  { min: 0, max: 20, count: 96 },
  { min: 20, max: 50, count: 104 },
  { min: 50, max: 100, count: 37 },
  { min: 100, max: 200, count: 12 },
  { min: 200, max: 500, count: 1 }
]
*/

const newBuildingHeights = await felt.getHistogramData({
    layerId: "buildings",
    attribute: "height",
    steps: [0, 20, 50, 100, 200, 500], // Same ranges as above
    values: {
        filters: ["year_built", "ge", 1950]
    }
});
/* returns:
[
  { min: 0, max: 20, count: 79 },
  { min: 20, max: 50, count: 99 },
  { min: 50, max: 100, count: 105 },
  { min: 100, max: 200, count: 24 },
  { min: 200, max: 500, count: 5 }
]
*/
```

**Comparing neighborhood density (Aggregates)**

```javascript
// Find average residential density across different neighborhoods
const downtownDensity = await felt.getAggregates({
    layerId: "buildings",
    boundary: [-122.335, 47.600, -122.330, 47.610], // downtown boundary
    filters: ["type", "eq", "residential"],
    aggregation: {
        method: "avg",
        attribute: "units_per_acre"
    }
});
// returns { avg: 124.7 }

const suburbanDensity = await felt.getAggregates({
    layerId: "buildings",
    boundary: [-122.200, 47.650, -122.150, 47.700], // suburban boundary
    filters: ["type", "eq", "residential"],
    aggregation: {
        method: "avg", 
        attribute: "units_per_acre"
    }
});
// returns { avg: 8.2 }
```

## Interactive visualization example

Here's how you might integrate these analysis methods with an interactive chart:

```javascript
// Create a pie chart showing building type distribution
async function createBuildingTypePieChart() {
    // Get data for the chart
    const data = await felt.getCategoryData({
        layerId: "buildings",
        attribute: "type"
    });
    
    // Render pie chart (using a hypothetical chart library)
    const chart = renderPieChart(data, {
        valuePath: "count",
        labelPath: "value",
        onSliceClick: handleSliceClick
    });
    
    return chart;
}

// Handle user interaction with the chart
async function handleSliceClick(slice) {
    const buildingType = slice.label;
    
    // Apply filter to highlight this building type on the map
    await felt.setLayerFilters({
        layerId: "buildings",
        filters: ["type", "eq", buildingType],
        note: `Showing ${buildingType} buildings only`
    });
    
    // Get additional statistics for this building type
    const stats = await felt.getAggregates({
        layerId: "buildings",
        filters: ["type", "eq", buildingType],
        aggregation: {
            method: "avg",
            attribute: "year_built"
        }
    });
    
    // Update the UI with these statistics
    updateStatsPanel(`Average ${buildingType} year built: ${Math.round(stats.avg)}`);
}

// Initialize the chart when the page loads
createBuildingTypePieChart();
```

This example demonstrates how a user clicking on a pie chart slice could apply a filter to the map, highlighting only the buildings of that type. It also shows how you could fetch additional statistics based on the user's selection to enrich the visualization experience.


# Drawing annotations

{% hint style="info" %}

#### Note: Annotations were previously referred to as Elements.

References have been updated in the app and documentation, while REST API endpoint and JS SDK method naming remain unchanged.
{% endhint %}

The Felt SDK provides two main approaches for creating annotations on your maps:

1. **Interactive Drawing**: Configure and activate drawing tools for users to create annotations manually
2. **Programmatic Creation**: Create and modify annotations directly through code

Annotations created via the SDK are session-specific - they're not persisted to the map and won't be visible to other users.

## Interactive drawing with tools

The methods on the [`ToolsController`](https://developers.felt.com/js-sdk-api-reference/tools/toolscontroller) enable you to programmatically activate drawing tools for your users, as well as setting various options for the tools, such as color, line width, etc.

Use the [`setTool`](https://developers.felt.com/js-sdk-api-reference/tools/toolscontroller#settool) method to activate a particular tool.

Use the [`setToolSettings`](https://developers.felt.com/js-sdk-api-reference/tools/toolscontroller#settoolsettings) method to configure the options for a specific tool.

Use the [`onToolChange`](https://developers.felt.com/js-sdk-api-reference/tools/toolscontroller#ontoolchange) and [`onToolSettingsChange`](https://developers.felt.com/js-sdk-api-reference/tools/toolscontroller#ontoolsettingschange) to be notified of changes to the above, or read them instantaneously with the [`getTool`](https://developers.felt.com/js-sdk-api-reference/tools/toolscontroller#gettool) and [`getToolSettings`](https://developers.felt.com/js-sdk-api-reference/tools/toolscontroller#gettoolsettings) methods.

As the user creates annotations with the tools, you can be notified of them being created and updated using the [`onElementCreate`](https://developers.felt.com/js-sdk-api-reference/elements/elementscontroller#onelementcreate) and [`onElementChange`](https://developers.felt.com/js-sdk-api-reference/elements/elementscontroller#onelementchange) listeners. See [#listening-for-annotation-creation](#listening-for-annotation-creation "mention") for more details.

### Tool types

<table><thead><tr><th width="166.3798828125">Tool name</th><th width="232.486328125">Annotation Type</th><th>Description</th></tr></thead><tbody><tr><td><code>pin</code></td><td>Place</td><td>Creates a single point on a map, with a symbol and optional label</td></tr><tr><td><code>line</code></td><td>Line</td><td>Creates a sequence of straight lines through the points that the user clicks</td></tr><tr><td><code>route</code></td><td>Line</td><td>Creates a line that follows the routing logic depending on the mode of transport selected. For instance, walking, driving and cycling routes follow applicable roads and pathways to reach the waypoints the user provides. Flying routes follow great circle paths.</td></tr><tr><td><code>polygon</code></td><td>Polygon</td><td>Creates an enclosed area with straight edges</td></tr><tr><td><code>circle</code></td><td>Circle</td><td>A circle is defined by its center and radius.</td></tr><tr><td><code>marker</code></td><td>Marker</td><td>Freeform drawing with a pen-like rendering. Different sizes can be set for the pen. The geometry produced is in world-space, so as you zoom the map, the pen strokes remain in place.</td></tr><tr><td><code>highlighter</code></td><td>Highlighter</td><td>Represents an area of interest, created by drawing with a thick pen. By default, drawing an enclosed shape fills the interior.</td></tr><tr><td><code>text</code></td><td>Text</td><td>A label placed on the map with no background color.</td></tr><tr><td><code>note</code></td><td>Note</td><td>A label placed on the map with a rectangular background color and either white or black text.</td></tr></tbody></table>

### Example

```javascript
// Configure the line tool
felt.setToolSettings({
  tool: "line",
  strokeWidth: 8,
  color: "#448C2A"
});

// Activate the line tool
felt.setTool("line");

// Later, deactivate the tool
felt.setTool(null);
```

## Programmatic annotation creation

If you want to create annotations programmatically instead of letting your users draw them interactively on the map, use the methods in the [`ElementsController`](https://developers.felt.com/js-sdk-api-reference/elements/elementscontroller).

To create annotations, use the [`createElement`](https://developers.felt.com/js-sdk-api-reference/elements/elementscontroller#createelement) method.

To update annotations, use the [`updateElement`](https://developers.felt.com/js-sdk-api-reference/elements/elementscontroller#updateelement) method.

To delete annotations, use the [`deleteElement`](https://developers.felt.com/js-sdk-api-reference/elements/elementscontroller#deleteelement) method.

When annotations are created programmatically, they also trigger notifications about the corresponding changes to annotations, via `onElementCreate`, `onElementChange` and `onElementDelete`.

### Example

```javascript

// Create a polygon
const polygonElement = await felt.createElement({
  type: "Polygon",
  coordinates: [
    [
      [-122.42, 37.78],
      [-122.41, 37.78],
      [-122.41, 37.77],
      [-122.42, 37.77],
      [-122.42, 37.78]
    ]
  ],
  color: "#FF5733",
  fillOpacity: 0.5
});


// Update its properties
await felt.updateElement({
  id: polygonElement.id,
  
  // note that we pass the type here, too in order to get correct
  // TypeScript type-checking and autocompletion.
  type: "Polygon",
  
  color: "#ABC123",
  fillOpacity: 0.5,
  strokeWidth: 2
});

// Finally delete the element
await felt.deleteElement(polygonElement.id)
```

## Retrieving annotation geometry <a href="#getting-elements" id="getting-elements"></a>

Extract the geometric representation of annotations using the [`getElementGeometry`](https://developers.felt.com/js-sdk-api-reference/elements/elementscontroller#getelementgeometry) method.

The geometry is returned in GeoJSON geometry format, which can be quite different to the way the annotation is specified in Felt. For example, `Circle` annotations in Felt have their geometry converted into a polygon, representing the area covered by the circle.

**Note:** Text, Note, and Image annotations do not return geometry as they are considered screen annotations rather than true "geospatial" annotations.

```javascript
// Get an element's geometry in GeoJSON format
const geometry = await felt.getElementGeometry("element-1");
console.log(geometry?.type, geometry?.coordinates);
```

## Listening for changes

Every change that is made to the annotations on a map results in a call to either [`onElementCreate`](https://developers.felt.com/js-sdk-api-reference/elements/elementscontroller#onelementcreate), [`onElementDelete`](https://developers.felt.com/js-sdk-api-reference/elements/elementscontroller#onelementdelete) or [`onElementChange`](https://developers.felt.com/js-sdk-api-reference/elements/elementscontroller#onelementchange).

```javascript
// Set up a listener for changes to a polygon
const unsubscribeChange = felt.onElementChange({
  options: { id: polygonElement.id },
  handler: ({element}) => {
    console.log("Polygon was updated:", element);
  }
});

// Set up a listener for deletion
const unsubscribeDelete = felt.onElementDelete({
  options: { id: polygonElement.id },
  handler: () => {
    console.log("Polygon was deleted");
  }
});

// Later, clean up listeners
unsubscribeChange();
unsubscribeDelete();
```

### Listening for annotation creation

There are two different ways for listening to annotations being created, and the one you use depends on how the annotation is being created, and at what point you want to know about an annotation's creation.

When the user is creating annotations with tools, they are often created in a number of steps, such as drawing a marker stroke or creating a polygon with many vertices.

When you want to know when the user has *finished* creating the annotation (e.g. the polygon was closed or the marker stroke ended) then you should use the [`onElementCreateEnd`](https://developers.felt.com/js-sdk-api-reference/elements/elementscontroller#onelementcreateend) listener.

When annotations are created programmatically, they do not trigger the [`onElementCreateEnd`](https://developers.felt.com/js-sdk-api-reference/elements/elementscontroller#onelementcreateend) event.

Annotations created using Tools *or* [`createElement`](https://developers.felt.com/js-sdk-api-reference/elements/elementscontroller#createelement) will trigger the [`onElementCreate`](https://developers.felt.com/js-sdk-api-reference/elements/elementscontroller#onelementcreate) event, with an extra property stating whether the annotation is still being created.

```javascript
// Listen for any element creation
const unsubscribe = felt.onElementCreate({
  handler: ({ element, isBeingCreated }) => {
    console.log(`New element created with ID: ${element?.id}`);

    // Check if the element is still being drawn
    if (isBeingCreated) {
      console.log("User is still creating this element");
    }
  }
});

// Or listen for when element creation is completed with a tool
const unsubscribeEnd = felt.onElementCreateEnd({
  handler: ({element}) => {
    console.log(`Element ${element.id} creation finished`);
  }
});

// Later, clean up listeners
unsubscribe();
unsubscribeEnd();
```

## Sample application: sending annotations drawn by users to your backend <a href="#creating-a-dynamic-drawing-interface" id="creating-a-dynamic-drawing-interface"></a>

Here is an example showing the power of the Felt SDK, where in just a few lines of code you can allow your users to draw annotations and have them sent to your own backend systems for persistence or analysis.

Assuming you have embedded your Felt map as described in [Getting started](/js-sdk/getting-started), and in your own UI you have added a `polygon-tool` button and a `reset-tool` button, all you need is the following:

```javascript
// Set your initial tool settings in a style that suits your application
felt.setToolSettings({
  tool: "polygon",
  strokeWidth: 2,
  color: "#FF5733",
  fillOpacity: 0.3,
});
  
// Activate the tool when the user clicks a button in your UI
document.getElementById("polygon-tool").addEventListener("click", () => {
  felt.setTool("polygon");
});

// Disable the tool when the user clicks a button in your UI
document.getElementById("reset-tool").addEventListener("click", () => {
  felt.setTool(null);
});

// Listen for completed polygons
felt.onElementCreateEnd({
  handler: async ({element}) => {
    // get the polygon geometry that the user just drew
    const geometry = await felt.getElementGeometry(element.id);
    
    // send the polygon to your own backend system
    sendToServer(geometry);
  }
});
```


# UI components

## Action triggers and custom panels

The Felt SDK enables you to extend Felt maps with custom UI components that integrate seamlessly with the native interface. These extensions allow you to add interactive controls and custom workflows directly within the map experience.

### UI extension points

Felt provides two primary ways to add custom UI to your maps:

<figure><img src="/files/IDJcX56RKnWlrLRR3ikP" alt=""><figcaption></figcaption></figure>

**Action Triggers** appear as buttons in the left sidebar and provide quick access to custom actions. Think of them as shortcuts that users can click to trigger specific functionality in your application.

**Custom Panels** appear in the right sidebar and offer a full canvas for complex UI. These panels can contain forms, controls, and interactive elements that work together to create sophisticated user experiences.

### Action triggers

Action triggers are simple button controls that execute custom functions when clicked. They're perfect for actions that don't require additional user input - like applying filters, running calculations, or enabling an interaction mode.

```javascript
await felt.createActionTrigger({
  actionTrigger: {
    label: "Check solar potential",
    onTrigger: async () => {
      // Enable polygon tool to allow a user to select a region
      await felt.setTool("polygon");
      // ...
    },
  }
});
```

### Custom panels

Custom panels provide a structured way to build complex UI within Felt. Each panel consists of three main sections that serve different purposes:

#### Panel structure

<figure><img src="/files/XEuCx15SaEpqT5PahW0C" alt=""><figcaption></figcaption></figure>

**Header** - Contains the panel title, and an optional close button.

**Body** - Houses the main interactive elements like forms, selectors, and content areas. This is where users spend most of their time interacting with your custom functionality.

**Footer** - Typically contains primary action buttons like "Save", "Cancel", or "Apply". This creates a consistent pattern users expect from dialog-style interfaces. The footer sticks to the bottom of the panel, with a divider separating it from the body.

#### Getting started with panels

Create a panel by first generating an ID, then specifying its contents. You can control where panels appear using the `initialPlacement` parameter. When `onClickClose` is specified, a close button will be rendered in the header.

```javascript
const panelId = await felt.createPanelId();
await felt.createOrUpdatePanel({
  panel: {
    id: panelId,
    title: "Add report",
    body: [
      { type: "Select", placeholder: "Choose a neighborhood", options: [{ label: "Downtown", value: "downtown" }] },
      { type: "Select", placeholder: "Choose a severity", options: [{ label: "Low", value: "low" }, { label: "High", value: "high" }] },
      { type: "TextInput", placeholder: "Email", onBlur: storeEmail },
    ],
    footer: [
      {
        type: "ButtonRow",
        align: "end",
        items: [
          { type: "Button", label: "Back", variant: "transparent", tint: "default", onClick: handleBack },
          { type: "Button", label: "Done", variant: "filled", tint: "primary", onClick: handleDone }
        ]
      }
    ],
    onClickClose: async () => {
      // Clean up
      await felt.deletePanel(panelId);
    }
  },
  initialPlacement: { at: "start" } // Optional: control panel positioning
});
```

### Panel elements

Custom panels support a variety of interactive and display elements that can be combined to create rich user experiences:

<figure><img src="/files/4Gu0KirrB0zVV9bmPl1H" alt=""><figcaption></figcaption></figure>

#### Text elements

[Text elements](https://developers.felt.com/js-sdk-api-reference/ui/uitextelement) display formatted content and support full Markdown rendering, allowing you to include headings, lists, links, and formatting within your panels.

```javascript
{
  type: "Text",
  content: "**Welcome!** This is a *formatted* text element with [links](https://felt.com).",
}
```

#### TextInput elements

[TextInput](https://developers.felt.com/js-sdk-api-reference/ui/uitextinputelement) elements allow users to enter custom values like names, descriptions, or numeric parameters.

```javascript
{
  type: "TextInput",
  placeholder: "First name",
  value: "",
  onChange: (args) => {
    console.log("New value:", args.value);
  },
}
```

#### Control elements

Control elements allow users to choose from predefined options:

<figure><img src="/files/FAEgXoLMRzQmVTf7uZgd" alt=""><figcaption></figcaption></figure>

Available control elements include [**Select**](https://developers.felt.com/js-sdk-api-reference/ui/uiselectelement), [**CheckboxGroup**](https://developers.felt.com/js-sdk-api-reference/ui/uicheckboxgroupelement), [**RadioGroup**](https://developers.felt.com/js-sdk-api-reference/ui/uiradiogroupelement), and [**ToggleGroup**](https://developers.felt.com/js-sdk-api-reference/ui/uitogglegroupelement). Each element supports similar properties:

```javascript
// Select dropdown
{
  type: "Select", // or "CheckboxGroup" | "RadioGroup" | "ToggleGroup"
  label: "Year",
  options: [
    { value: "2025", label: "2025" },
    { value: "2024", label: "2024" },
  ],
  value: "",
  onChange: (args) => {
    console.log("Selected:", args.value);
  }
}
```

#### Button elements

[Button elements](https://developers.felt.com/js-sdk-api-reference/ui/uibuttonelement) trigger actions and come in different styles to communicate their importance and effect. Buttons can have different variants (filled, outlined, and transparent) and tints (primary, accent, danger and default):

<figure><img src="/files/47FriEfkggdqj5lsqf7f" alt=""><figcaption></figcaption></figure>

Primary filled buttons highlight the most important action in a context. Use sparingly - typically one per panel section.

```javascript
{
  type: "Button",
  label: "Submit",
  variant: "filled", // "transparent" | "outlined"
  tint: "primary", // "default" | "accent" | "danger"
  onClick: async () => {
    // Handle button click
  }
}
```

#### Button rows

Group related buttons together to create clear action hierarchies:

<figure><img src="/files/PxxDgedNomeu24VsHmKq" alt=""><figcaption></figcaption></figure>

[Button rows](https://developers.felt.com/js-sdk-api-reference/ui/uibuttonrowelement) automatically handle spacing and alignment, ensuring your panels look polished and consistent.

```javascript
{
  type: "ButtonRow",
  align: "end",
  items: [
    { type: "Button", label: "Clear", variant: "transparent", tint: "default", onClick: handleClear },
    { type: "Button", label: "Send report", variant: "filled", tint: "primary", onClick: handleSend }
  ]
}
```

#### Grid elements

[The grid element](https://developers.felt.com/js-sdk-api-reference/ui/uigridcontainerelement) helps organize elements within panels to create complex layouts. It uses a `grid` property that follows the same syntax as the CSS shorthand grid property, and includes `verticalAlignment` and `horizontalDistribution` properties for precise control over layout positioning.

<figure><img src="/files/YGo0EusJ5p9dmAmTiMog" alt=""><figcaption></figcaption></figure>

```javascript
{
  type: "Grid",
  grid: "auto-flow / 2fr 1fr", // CSS grid shorthand
  verticalAlignment: "start",
  horizontalDistribution: "stretch",
  items: [
    { type: "Text", content: "![image](https://example.com/image1.png)" },
    { type: "Text", content: "![image](https://example.com/image2.png) \n ![image](https://example.com/image3.png)" },
  ]
}
```

#### iframe elements

[iframe elements](https://developers.felt.com/js-sdk-api-reference/ui/uiiframeelement) allow you to embed external content by providing a URL to charts, dashboards, or other web applications directly within your panels.

```javascript
{
  type: "Iframe",
  url: "https://example.com/dashboard",
  height: 400,
}
```

#### Divider elements

Divider elements provide visual separation between sections of content in your panels.

```javascript
{
  type: "Divider",
}
```

### Panel state management

#### Creating and updating panels

A panel is identified by its ID, which must be created using [`createPanelId`](https://developers.felt.com/js-sdk-api-reference/main/feltcontroller#createpanelid). Custom IDs are not supported to prevent conflicts with other panels.\
\
Use [`createOrUpdatePanel`](https://developers.felt.com/js-sdk-api-reference/main/feltcontroller#createorupdatepanel) for most panel scenarios. This declarative method lets you specify what the panel should contain, and it handles both creating new panels and updating existing ones with the same API call.

```javascript
const panelId = await felt.createPanelId();
const greetingElement = { id: "greeting", type: "Text", content: "Hello" };

// Initial state
await felt.createOrUpdatePanel({
  panel: {
    id: panelId,
    title: "My Panel",
    body: [greetingElement]
  }
});

// Update using destructuring
await felt.createOrUpdatePanel({
  panel: {
    id: panelId,
    title: "My Panel",
    body: [{ ...greetingElement, content: "Hello World" }]
  }
});
```

#### Targeted panel element updates

Use [`updatePanelElements`](https://developers.felt.com/js-sdk-api-reference/main/feltcontroller#updatepanelelements) for granular control when you want to modify individual elements. Elements need IDs to be targeted for updates. You can also use [`createPanelElements`](https://developers.felt.com/js-sdk-api-reference/main/feltcontroller#createpanelelements) to add elements and [`deletePanelElements`](https://developers.felt.com/js-sdk-api-reference/main/feltcontroller#deletepanelelements) to remove elements by their IDs.

```javascript
const panelId = await felt.createPanelId();

// Create panel with multiple elements
await felt.createOrUpdatePanel({
  panel: {
    id: panelId,
    title: "Data Panel",
    body: [
      { id: "status-text", type: "Text", content: "Ready" },
      { 
        id: "layer-select", 
        type: "Select", 
        label: "Choose Layer",
        options: [
          { value: "layer1", label: "Population" },
          { value: "layer2", label: "Income" }
        ]
      }
    ]
  }
});

// Update only the text element
await felt.updatePanelElements({
  panelId,
  elements: [{
    element: {
      id: "status-text",
      type: "Text",
      content: "Processing data..."
    }
  }]
});

// Add a new element to the panel
await felt.createPanelElements({
  panelId,
  elements: [{
    element: { 
      id: "progress-text", 
      type: "Text", 
      content: "Progress: 50%" 
    },
    container: "body",
    placement: { at: "end" }
  }]
});

// Remove an element from the panel
await felt.deletePanelElements({
  panelId,
  elements: ["progress-text"]
});
```


# Events reference

Every listener on the Felt controller follows the same pattern: you pass a `handler` (and sometimes an `options` object scoping the listener to a specific entity), and the call returns an unsubscribe function.

```typescript
const unsubscribe = felt.onLayerChange({
  options: { id: "layer-1" },
  handler: ({ layer }) => console.log(layer.bounds),
});

// ...later, when you no longer need the listener
unsubscribe();
```

Always call the unsubscribe function when your UI unmounts or the listener is no longer needed.

## Viewport and map state

| Listener            | Scoping options | Handler receives                                                                                             |
| ------------------- | --------------- | ------------------------------------------------------------------------------------------------------------ |
| `onViewportMove`    | —               | `ViewportState` — fires continuously during movement, including during animations and inertia.               |
| `onViewportMoveEnd` | —               | `ViewportState` — fires once when dragging, zooming, animations, and inertia have finished.                  |
| `onMapIdle`         | —               | Nothing. Fires when the map is fully idle: no transitions, no interaction, all tiles loaded, fades complete. |
| `onBasemapChange`   | —               | `Basemap` — the new basemap.                                                                                 |

## Annotations (elements)

| Listener               | Scoping options | Handler receives                                                                                                                                                          |
| ---------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `onElementCreate`      | —               | `{ element: Element \| null, isBeingCreated: boolean }` — fires repeatedly while the user draws (`isBeingCreated: true`), then a final time with `isBeingCreated: false`. |
| `onElementCreateEnd`   | —               | `{ element: Element }` — fires once, when creation is finished.                                                                                                           |
| `onElementChange`      | `{ id }`        | `{ element: Element \| null, isBeingCreated: boolean }` — `element` is `null` if it was removed.                                                                          |
| `onElementDelete`      | `{ id }`        | Nothing.                                                                                                                                                                  |
| `onElementGroupChange` | `{ id }`        | `{ elementGroup: ElementGroup \| null }`                                                                                                                                  |

## Layers and legends

| Listener                  | Scoping options   | Handler receives                                                                                                                                      |
| ------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `onLayerChange`           | `{ id }`          | `{ layer: Layer \| null }`                                                                                                                            |
| `onLayerGroupChange`      | `{ id }`          | `{ layerGroup: LayerGroup \| null }`                                                                                                                  |
| `onLegendItemChange`      | `{ id, layerId }` | `{ legendItem: LegendItem \| null }`                                                                                                                  |
| `onLayerFiltersChange`    | `{ layerId }`     | `LayerFilters` — the bare filters object (`{ style, components, ephemeral, combined }`), with no wrapper. See [Layer filters](/js-sdk/layer-filters). |
| `onLayerBoundariesChange` | `{ layerId }`     | `LayerBoundaries \| null`                                                                                                                             |

## Pointer and selection

| Listener            | Scoping options | Handler receives                                                                                                                                             |
| ------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `onPointerClick`    | —               | `MapInteractionEvent` — `{ coordinate, point, features, rasterValues }`. `features` and `rasterValues` are arrays (empty when nothing is under the pointer). |
| `onPointerMove`     | —               | `MapInteractionEvent`                                                                                                                                        |
| `onSelectionChange` | —               | `{ selection: EntityNode[] }` — see [Working with selection](/js-sdk/working-with-selection).                                                                |

## Tools

| Listener               | Scoping options | Handler receives                                                                                             |
| ---------------------- | --------------- | ------------------------------------------------------------------------------------------------------------ |
| `onToolChange`         | —               | `ToolType \| null` — the newly selected tool, or `null` when no tool is active.                              |
| `onToolSettingsChange` | —               | `ToolSettingsChangeEvent` — a discriminated union: check the `tool` field to narrow to that tool's settings. |

For payload type details, follow each method's entry in the [API reference](https://developers.felt.com/js-sdk-api-reference).


# Sample application

This is a sample application showing how to use the Felt SDK to build an app with the following features:

* listing the map's layers
* toggling layer visibility
* moving the viewport to center it on predefined city locations

<figure><img src="/files/F31GsGl9aNC0dQL9ZMx5" alt=""><figcaption><p>The sample Felt SDK Application</p></figcaption></figure>

The commented code in its entirety is shown below.

```html
<!doctype html>
<html lang="en">
  <head>
    <title>Felt JS SDK</title>
    <style>
      body {
        margin: 0;
        padding: 0;
        font-family: sans-serif;
        font-size: 13px;
      }

      .container {
        display: grid;
        grid-template-columns: 1fr 240px;
        height: 100vh;
      }

      iframe {
        display: block;
      }

      #sidebar {
        padding: 1rem;
        user-select: none;
      }

      #markers {
        margin-bottom: 1rem;
        padding-bottom: 1rem;
        border-bottom: 1px solid #ccc;
      }

      .marker {
        cursor: pointer;
        padding: 0.25rem 0;
      }

      .layer-toggles_toggle {
        padding: 0.25rem 0;
        margin-left: -4px;
        display: flex;
        align-items: center;
        gap: 0.25rem;
      }

      h3 {
        margin: 0;
        margin-bottom: 0.5rem;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div id="mapContainer"></div>
      <div id="sidebar">
        <div id="markers">
          <h3>Cities</h3>
        </div>
        <div id="layers">
          <h3>Layers</h3>
        </div>
      </div>
    </div>

    <script type="module">
      // Load the Felt SDK from the jsDelivr CDN
      import { Felt } from "https://esm.run/@feltmaps/js-sdk";

      // Get the map and sidebar elements
      const container = document.getElementById("mapContainer");
      const markerContainer = document.getElementById("markers");
      const layerContainer = document.getElementById("layers");

      // Embed the map — replace this ID with your own map's ID
      const felt = await Felt.embed(container, "u49BWs5EtSI29CpwuwB9CzRiC", {
        uiControls: {
          showLegend: false,
          cooperativeGestures: false,
          fullScreenButton: false,
        },
      });

      // Add some cities to the sidebar
      const locations = [
        { name: "Oakland", lat: 37.8044, lng: -122.271 },
        { name: "New York", lat: 40.7128, lng: -74.006 },
        { name: "Los Angeles", lat: 34.0522, lng: -118.2437 },
        { name: "Chicago", lat: 41.8781, lng: -87.6298 },
        { name: "Houston", lat: 29.7604, lng: -95.3698 },
        { name: "Phoenix", lat: 33.4484, lng: -112.074 },
      ];

      locations.forEach((location) => {
        // create a DOM element with the city name
        const marker = document.createElement("div");
        marker.classList.add("marker");
        marker.innerText = location.name;

        // center the viewport on the city when the marker is clicked
        marker.addEventListener("click", () => {
          felt.setViewport({
            center: {
              latitude: location.lat,
              longitude: location.lng,
            },
            zoom: 10,
          });
        });

        // add the marker to the sidebar
        markerContainer.appendChild(marker);
      });

      // get all the layers
      felt.getLayers().then((layers) => {
        layers.forEach((layer) => {
          // create a DOM element to represent the layer
          const layerElement = document.createElement("div");
          layerElement.classList.add("layer-toggles_toggle");
          layerElement.innerHTML = `
            <input type="checkbox" id="${layer.id}" ${
              layer.visible ? "checked" : ""
            }>
            <label for="${layer.id}">${layer.name}</label>
          `;

          // toggle the layer's visibility when the checkbox changes
          const checkbox = layerElement.querySelector("input");
          checkbox.addEventListener("change", () => {
            felt.setLayerVisibility(
              checkbox.checked ? { show: [layer.id] } : { hide: [layer.id] },
            );
          });

          // let the map be the source of truth: keep the checkbox in
          // sync if the layer's visibility changes for any other reason
          felt.onLayerChange({
            options: { id: layer.id },
            handler: ({ layer }) => {
              if (layer) checkbox.checked = layer.visible;
            },
          });

          // add the layer element to the container
          layerContainer.appendChild(layerElement);
        });
      });
    </script>
  </body>
</html>
```


# Examples

Explore examples of what you can build with the Felt SDK. These examples showcase different approaches to creating interactive map experiences - from extensions that run directly within Felt to embedded maps in custom applications.

## Extensions

Extensions run directly within Felt maps, giving you immediate access to all SDK functionality. Here are some examples built using AI assistance:

### Commuter patterns

Visualize transportation patterns by drawing lines to destination counties on click. Features travel mode options and filtering capabilities to analyze commuting data across different regions. View the map [here](https://felt.com/map/Commuting-patterns-EPs9A4ShcT8i0pErry2vayB).

<figure><img src="/files/xzAqXuHF84RE3hmLSzZQ" alt=""><figcaption></figcaption></figure>

### Neighborhood comparison

Compare neighborhoods side-by-side with automated analysis of land use patterns. This tool helps users understand demographic and geographic differences between areas. View the map [here](https://felt.com/map/Compare-Neighborhoods-Yn2fUt8fQJKBp9A29ARlOvED).

<figure><img src="/files/hHmiTE7B81sEpRLreA4b" alt=""><figcaption></figcaption></figure>

### Animated data

Bring geographic data to life with animations showing the flow of the Mississippi River Basin from headwaters to the Gulf of Mexico, demonstrating how to create compelling temporal visualizations. View the map [here](https://felt.com/map/Mississippi-River-Basin-Animation-PBTjZpYDQHabIOpuZeADcD).

<figure><img src="/files/BiilPFYnuG4MgchOlErd" alt=""><figcaption></figcaption></figure>

### Story map

Guide users through agricultural regions with an interactive narrative experience that combines storytelling with geographic exploration. View the map [here](https://felt.com/map/CropScape-Explorer-8mXNgodOTty9BHKEgP7m9CLA).

<figure><img src="/files/IYGcUDpino4I2TfGgrJ0" alt=""><figcaption></figcaption></figure>

## Embedded maps

Embed Felt maps in your own applications and control them with the SDK. Here are some examples built with React and hosted on CodeSandbox:

### Rooftop solar potential

This interactive application leverages the Tool API to enable users to draw custom geometries that retrieve filtered GeoJSON data from an ESRI FeatureService. The application creates a dynamically styled GeoJSON layer to visualize solar potential data, helping users identify optimal locations for solar installations. View the app and code [here](https://7wvdgn.csb.app/).

<figure><img src="/files/IshRDNV7N9truZ4ujrS4" alt=""><figcaption></figcaption></figure>

### Sales dashboard

Create powerful business intelligence tools by combining Felt's layer statistics with popular charting libraries. This example demonstrates how to build interactive visualizations that leverage layer filters. View the app and code [here](https://ydx2p7.csb.app/).

<figure><img src="/files/pgahPUUCxn3bRiGj33uG" alt=""><figcaption></figcaption></figure>

### Custom legend with nested folders

Enhance map usability with a custom legend that extracts and uses [FSL](/felt-style-language/getting-started) styling information to generate SVG icons. The code demonstrates how to build a nested folder structure with visibility toggles using layer filters, providing a pattern for organizing complex data layers. View the app and code [here](https://x7ycff.csb.app/).

<figure><img src="/files/OSQD4YtBT5Dqrw8Ghawg" alt=""><figcaption></figcaption></figure>

### Inset maps

Build comprehensive multi-view dashboards by embedding multiple Felt maps on a single page. This example demonstrates how to create synchronized map views that communicate with each other, enabling users to simultaneously view different geographic contexts or zoom levels of the same data. View the app and code [here](https://wm6k24.csb.app/).

<figure><img src="/files/vOALgO7rmuGv1t1f376Z" alt=""><figcaption></figcaption></figure>

### Lens map

Create engaging interactive experiences with customizable map lenses that reveal different data layers or styling as users explore. This technique allows for compelling before/after comparisons or the ability to highlight specific data attributes within a defined area. View the app and code [here](https://84mwly.csb.app/).

<figure><img src="/files/kUKUd2BK6kKKUqJfkkrC" alt=""><figcaption></figcaption></figure>

### Isochrones

Provides a pattern for integrating third-party geospatial APIs with Felt maps. This example demonstrates how to make API requests based on annotation geometry and map interactions, process the returned data, and visualize isochrones as dynamic layers. View the app and code [here](https://t7x4rl.csb.app/).

<figure><img src="/files/tz7B18BlB8YEtHuLbCcE" alt=""><figcaption></figcaption></figure>


# Getting started

The Felt Style Language (FSL) is a JSON-based specification for styling data layers in Felt — comparable to the Mapbox Style Spec, but at the layer level. A single JSON object describes how a layer is drawn (`paint`), labeled (`label`), classified (`config`), and presented in popups and legends.

## Where you use FSL

The same style object works in three places:

* **In the Felt app** — open a layer's overflow menu and choose `Actions > Edit style language` to view and edit the layer's FSL directly. (The neighbouring `Actions > Edit styles` opens the visual style editor, which has no JSON view.)
* **Via the REST API** — read a layer's `style` field and update it with [`POST /update_style`](/rest-api/styling-layers).
* **Via the JS SDK** — apply session-only style changes with [`setLayerStyle`](https://developers.felt.com/js-sdk-api-reference/layers/layerscontroller#setlayerstyle).

## Your first style

Every style needs a `version` and a `type`. Here is a complete, minimal style that draws a point layer in green with a visible stroke:

```json
{
  "version": "2.3.1",
  "type": "simple",
  "config": {},
  "paint": {
    "color": "#28A745",
    "size": 8,
    "strokeColor": "auto",
    "strokeWidth": 1
  },
  "legend": {},
  "label": {},
  "popup": {}
}
```

Paste this into `Actions > Edit style language` on any point layer and the points turn green immediately. From here, styling is incremental: change `"type"` to `"categorical"` or `"numeric"` to drive color or size from your data, add `steps` or `categories` to `config`, and swap literal colors for [`@palette` shortcuts](/felt-style-language/colors-and-palettes).

## Anatomy of a style

| Key          | Purpose                                                                                                                                                                                     |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `version`    | The FSL version the style is written against. Use `"2.3.1"` (current). Older versions are accepted and migrated automatically.                                                              |
| `type`       | The visualization type: `simple`, `categorical`, `numeric`, `heatmap`, `h3`, or `hillshade`. See [Types of visualizations](/felt-style-language/types-of-visualizations).                   |
| `config`     | Data configuration: which attribute drives the visualization, class breaks, categories, aggregation. See [The config block](/felt-style-language/style-definition-blocks/the-config-block). |
| `paint`      | How geometries and pixels are drawn: colors, sizes, strokes, opacity, icons. See [The paint block](/felt-style-language/style-definition-blocks/the-paint-block).                           |
| `label`      | Feature labels: fonts, halos, placement, zoom range. See [The label block](/felt-style-language/style-definition-blocks/the-label-block).                                                   |
| `popup`      | What clicking a feature shows. See [The popup block](/felt-style-language/style-definition-blocks/the-popup-block).                                                                         |
| `attributes` | Display names and number formatting for attributes. See [The attributes block](/felt-style-language/style-definition-blocks/the-attributes-block).                                          |
| `filters`    | Which features are visible. See [The filters block](/felt-style-language/style-definition-blocks/the-filters-block).                                                                        |
| `legend`     | Legend labels. See [Legends](/felt-style-language/legends).                                                                                                                                 |

## Explore the language

### Style definition blocks

Learn how to define and configure the code blocks that compose the Felt Style Language

{% content-ref url="/pages/ia98n0VZ2bW0rDUZjF49" %}
[Style definition blocks](/felt-style-language/style-definition-blocks)
{% endcontent-ref %}

### Types of visualizations

Learn about visualization types, including simple, categorical, numeric (color by & size by), heatmaps, H3, and raster (imagery, numeric, categorical, and hillshade).

{% content-ref url="/pages/t1uG4exfWZRod1VeBRFi" %}
[Types of visualizations](/felt-style-language/types-of-visualizations)
{% endcontent-ref %}

### Colors, icons & classification

Shared building blocks referenced throughout the language: smart `"auto"` colors and named palettes, the icon catalog, and the classification methods used to turn numbers into classes.

{% content-ref url="/pages/T2CzLFRxCIMHaz4WjYm8" %}
[Colors & palettes](/felt-style-language/colors-and-palettes)
{% endcontent-ref %}

{% content-ref url="/pages/x4VFtWKNEKYOiqWp7dAQ" %}
[Icons](/felt-style-language/icons)
{% endcontent-ref %}

{% content-ref url="/pages/fdqlMScWxZO7qFHImPxT" %}
[Classification methods](/felt-style-language/classification-methods)
{% endcontent-ref %}

### Zoom-based styling

Vary paint and label properties with the map's zoom level using interpolators.

{% content-ref url="/pages/wSQReOdXoeF1OhZv1hts" %}
[Zoom-based styling](/felt-style-language/zoom-based-styling)
{% endcontent-ref %}

### Legends

Details on how to customize legends on a per-layer basis.

{% content-ref url="/pages/1fKJtssIMYNDGoHaGUKl" %}
[Legends](/felt-style-language/legends)
{% endcontent-ref %}

### Errors

Definitions for errors raised when validating the Felt Style Language.

{% content-ref url="/pages/X4xQjPoTGzipaOEgV5zN" %}
[Errors](/felt-style-language/errors)
{% endcontent-ref %}

### Examples

A compact gallery with one worked example per visualization type.

{% content-ref url="/pages/BXFcwzVszq56jUVsZcNT" %}
[Examples](/felt-style-language/examples)
{% endcontent-ref %}


# Style definition blocks

### The shape of a style definition

A style in its most basic form contains a version definition but it can be extended to define how we want geometry and labels to show on the map, how the legend should look like, what information is shown in popups and the formatting used when displaying feature properties.

The table below describes which properties can be used in the style definition.

<table><thead><tr><th width="192">Field name</th><th>Description</th></tr></thead><tbody><tr><td><code>version</code></td><td>Mandatory. Defines which version this style adheres to. The current version is <code>"2.3.1"</code>.</td></tr><tr><td><code>type</code></td><td>Optional. One of <a href="/pages/5UFembV8TweBItxmk9wx">simple</a>, <a href="/pages/OOI5SjNpmkX76Zi9y8Wi">categorical</a>, <a href="/pages/l19xNEDSN3qnKxQpzWjT">numeric</a>, <a href="/pages/hvXsaskqTHe4aJVaytZe">heatmap</a>, <a href="/pages/XuRidNtEaH2b1GsEpC8H">h3</a>, or (for raster) <a href="/pages/B2USz8szefSFUzs37eev">hillshade</a>. Defaults to simple.</td></tr><tr><td><code>config</code></td><td>Optional. A block that contains some configuration options to be used across the style. <a href="/pages/oKOUl53nPGg06x4Juhxh">Learn more</a>.</td></tr><tr><td><code>paint</code></td><td>Optional. An object (or array of objects) that defines how the data will be drawn. <a href="/pages/FCz1h2KbnMO7kJzIwKYK">Learn more.</a></td></tr><tr><td><code>label</code></td><td>Optional. An object that defines how the labels will be drawn. <a href="/pages/SP3VRafkNoLDOkMRewn2">Learn more.</a></td></tr><tr><td><code>legend</code></td><td>Optional. Defines how this layer will be shown on the layer panel. <a href="/pages/1fKJtssIMYNDGoHaGUKl">Learn more.</a></td></tr><tr><td><code>popup</code></td><td>Optional. Defines how the popup is shown and what’s included. <a href="/pages/5qsPLbZ2MQGtiV4gQH1Z">Learn more.</a></td></tr><tr><td><code>attributes</code></td><td>Optional. Defines how attributes are shown both in the popup and the table. <a href="/pages/k9QLQWI0KRfVYSsCwJjh">Learn more.</a></td></tr><tr><td><code>filters</code></td><td>Optional. A data filter definition that defines which data will be rendered. <a href="/pages/Aj7Mw36t9oq0LufZ2qet">Learn more.</a></td></tr></tbody></table>

All of these keys are siblings at the top level of the object — none are nested inside another:

```json
{
  "version": "2.3.1",
  "type": "simple",
  "config": {},
  "paint": {},
  "label": {},
  "legend": {},
  "popup": {}
}
```

{% hint style="info" %}
The block that controls how geometry and raster pixels are drawn is named `paint`. (Some earlier drafts of this guide referred to it as `style`.)
{% endhint %}


# The config block

The config block contains configuration options for a given visualization.

These are the fields that each config block can contain:

| Field name             | Description                                                                                                                                                                                                                                                                                                                                                   |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `aggregation`          | Optional, for [H3](/felt-style-language/types-of-visualizations/h3) visualizations. How points are aggregated within each cell: `"count"` (default), `"sum"`, `"mean"`, `"min"`, or `"max"`. All values except `"count"` also require `numericAttribute`.                                                                                                     |
| `band`                 | Optional. Used in raster numeric visualizations. The raster band (1-indexed) to read data from — a number, or an array of band numbers for multiband styling.                                                                                                                                                                                                 |
| `baseBinLevel`         | Used in [H3](/felt-style-language/types-of-visualizations/h3) visualizations. The [H3 cell resolution](https://h3geo.org/docs/core-library/restable/) (0–15). Country-scale ≈ 3–4, city-scale ≈ 6–7. Sets the cell size when `binMode` is `"fixed"`; in the zoom-adaptive modes it serves as the reference resolution that `steps` values are scaled against. |
| `binMode`              | Used in [H3](/felt-style-language/types-of-visualizations/h3) visualizations. `"fixed"` (default; uses `baseBinLevel` at all zooms) or the zoom-adaptive modes `"low"`, `"medium"`/`"auto"`, and `"high"` — low means larger hexagons, high means smaller ones.                                                                                               |
| `categoricalAttribute` | Mandatory for vector categorical visualizations. The attribute that contains the categorical values that will be used. (Raster categorical layers classify raw pixel values instead and omit this field.)                                                                                                                                                     |
| `categories`           | Mandatory for a categorical visualization. Either an explicit array of category values, or a shortcut: `{"type": "top", "count": N}` for the N most common values, `{"type": "bottom", "count": N}` for the N least common, or `{"type": "all"}` (raster) for every unique pixel value.                                                                       |
| `labelAttribute`       | Optional. Defines which dataset attribute or attributes to use for labeling. If multiple values are provided, the first available one will be used.                                                                                                                                                                                                           |
| `method`               | Optional. Used in multiband raster numeric visualizations. Maps a spectral index (NDVI/NDMI/NDWI) to its band assignments. See [Raster visualizations](/felt-style-language/types-of-visualizations/raster).                                                                                                                                                  |
| `noData`               | Optional. Used in raster visualizations. A value or array of values that won’t be shown (e.g. `[-9999]` for voids, `[0]` for no-data).                                                                                                                                                                                                                        |
| `numericAttribute`     | Mandatory for a numeric visualization. The attribute that contains the numeric values used.                                                                                                                                                                                                                                                                   |
| `otherOrder`           | Optional. Used in categorical visualizations. It can be set to either "below" or "above" to make features that do not match any of the defined categories render below or above the other ones. The default position is "below".                                                                                                                              |
| `outOfRangeValues`     | Optional. Used in raster numeric visualizations. `"color"` clamps out-of-range pixels to the nearest class color; `"hide"` makes them transparent.                                                                                                                                                                                                            |
| `rasterResampling`     | Optional. Used in raster numeric (and tinted hillshade) visualizations — not valid for categorical rasters, which always resample nearest. `"nearest"` preserves exact pixel values; `"linear"` smoothly interpolates (use for continuous imagery/elevation). Set it explicitly rather than relying on a default.                                             |
| `showOther`            | Optional. Used in categorical visualizations. If set to true it shows all features that do not match any defined category and adds an extra entry as the last item in the legend.                                                                                                                                                                             |
| `steps`                | Used by numeric, H3, and tinted-hillshade visualizations. Either an explicit array of break points (at least two values), or a [classification](/felt-style-language/classification-methods) shortcut such as `{"type": "jenks", "count": 5}` or `{"type": "continuous"}`.                                                                                    |

## Examples

`config` is always an object. The examples below show the config block in isolation.

{% code title="Categorical config — explicit categories" %}

```json
"config": {
  "labelAttribute": ["Wikipedia", "faa"],
  "categoricalAttribute": "faa",
  "categories": ["faa-code-1", "faa-code-2", "faa-code-3"],
  "showOther": true,
  "otherOrder": "above"
}
```

{% endcode %}

{% code title="Categorical config — top-N shortcut" %}

```json
"config": {
  "categoricalAttribute": "surface",
  "categories": {"type": "top", "count": 5},
  "showOther": true
}
```

{% endcode %}

{% code title="Vector numeric config — automatic classification" %}

```json
"config": {
  "numericAttribute": "percentage",
  "steps": {"type": "jenks", "count": 5}
}
```

{% endcode %}

{% code title="Raster numeric config — single band" %}

```json
"config": {
  "band": 1,
  "steps": {"type": "continuous"},
  "noData": [-9999],
  "rasterResampling": "nearest"
}
```

{% endcode %}

{% code title="H3 config — aggregate points into hexes" %}

```json
"config": {
  "aggregation": "sum",
  "numericAttribute": "capacity_mw",
  "baseBinLevel": 4,
  "binMode": "medium",
  "steps": {"type": "quantiles", "count": 5}
}
```

{% endcode %}


# The paint block

{% hint style="info" %}
The paint block defines how feature geometries and raster pixels are rendered.
{% endhint %}

Color properties (`color`, `strokeColor`) accept a literal color or a `@palette` shortcut, and `strokeColor` additionally accepts the smart keyword `"auto"` — see [Colors & palettes](/felt-style-language/colors-and-palettes). Point layers can be drawn as icons via `iconImage` — see [Icons](/felt-style-language/icons). For data-driven (`categorical`, `numeric`, `h3`) visualizations, paint properties may be arrays, one value per category/class.

Properties common to all visualization types.

|                          | Type     | Default | Description                                                                                                                                                                                                                                                                                                                                                                     |
| ------------------------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `isClickable`            | boolean  | true    | Optional. A flag to tell if features should be clickable                                                                                                                                                                                                                                                                                                                        |
| `isHoverable`            | boolean  | false   | Optional. A flag to tell if features should be hoverable                                                                                                                                                                                                                                                                                                                        |
| `isSandwiched`           | boolean  | false   | Optional. A flag to tell if features affected by this visualization need to be rendered below the basemap road and water layers. Only applies to polygon features, point and line features are already rendered on top of the basemap                                                                                                                                           |
| `maxZoom`                | number   | 24      | Optional. The maximum zoom level at which the visualization will be shown                                                                                                                                                                                                                                                                                                       |
| `minZoom`                | number   | 0       | Optional. The minimum zoom level at which the visualization will be shown                                                                                                                                                                                                                                                                                                       |
| `renderAsLines`          | boolean  | false   | Optional. Decides if a polygon dataset should be rendered as lines thus making them render above the basemap. Note that using this requires that the style uses line properties instead of polygon ones.                                                                                                                                                                        |
| `paintPropertyOverrides` | `object` |         | Optional. An escape hatch: raw [MapLibre](https://maplibre.org/maplibre-style-spec/) `paint` or `layout` properties applied to the generated layer. Keys that are not valid for the underlying MapLibre layer type are silently ignored. Also available on the `label` block. See [here](https://help.felt.com/layers/styling/vector-layers#maplibre-style-overrides) for more. |

## Simple visualizations

The following properties are available for the `simple` type of visualization

<table><thead><tr><th width="254"></th><th width="114">Type</th><th width="203">Applies to</th><th>Description</th></tr></thead><tbody><tr><td><code>color</code></td><td>string | <a href="/pages/EjwMClndeRGhtqdOEZUu">Interpolator</a></td><td>Points, lines and polygons</td><td>Optional. The color to be used</td></tr><tr><td><code>dashArray</code></td><td>number[]</td><td>Lines</td><td>Optional. The dash line definition — an array of dash/gap lengths with an even number of entries, e.g. <code>[2, 1]</code></td></tr><tr><td><code>highlightColor</code></td><td>string</td><td>Points, lines and polygons</td><td>Optional. The color to be used when a feature is selected</td></tr><tr><td><code>highlightStrokeColor</code></td><td>string</td><td>Points, lines and polygons</td><td>Optional. The stroke color to be used when a feature is selected</td></tr><tr><td><code>highlightStrokeWidth</code></td><td>number | <a href="/pages/EjwMClndeRGhtqdOEZUu">Interpolator</a></td><td>Points and polygons</td><td>Optional. The stroke width when a feature is selected</td></tr><tr><td><code>lineCap</code></td><td><code>"butt"</code> | <code>"round"</code> | <code>"square"</code>| <a href="/pages/EjwMClndeRGhtqdOEZUu">Interpolator</a></td><td>Lines</td><td>Optional. The shape used to draw the end points of lines</td></tr><tr><td><code>lineJoin</code></td><td><code>"bevel"</code> | <code>"round"</code>| <code>"miter"</code>| <a href="/pages/EjwMClndeRGhtqdOEZUu">Interpolator</a></td><td>Lines</td><td>Optional. The shape used to join two line segments when they meet</td></tr><tr><td><code>opacity</code></td><td>number | <a href="/pages/EjwMClndeRGhtqdOEZUu">Interpolator</a></td><td>Points, lines and polygons</td><td>Optional. The opacity to use from 0 to 1</td></tr><tr><td><code>size</code></td><td>number | <a href="/pages/EjwMClndeRGhtqdOEZUu">Interpolator</a></td><td>Points and lines</td><td>Optional. Point radius or line width in pixels</td></tr><tr><td><code>strokeColor</code></td><td>string | <a href="/pages/EjwMClndeRGhtqdOEZUu">Interpolator</a> | <code>auto</code></td><td>Points and polygons</td><td>Optional. Stroke color</td></tr><tr><td><code>strokeWidth</code></td><td>number | <a href="/pages/EjwMClndeRGhtqdOEZUu">Interpolator</a></td><td>Points and polygons</td><td>Optional. Stroke width in pixels</td></tr></tbody></table>

See [default values](#default-values) for the default values of these attributes on each geometry type.

## Categorical and numeric visualizations

`categorical` and `numeric` visualizations use the same `color`, `opacity`, `size`, `strokeColor`, `strokeWidth`, and line properties listed above (`highlightColor`/`highlightStrokeColor` are simple-only). The difference is that each property may be an **array**: a single value applies to every category/class, or one value per category/class. See the [categorical](/felt-style-language/types-of-visualizations/categorical-visualizations) and [numeric](/felt-style-language/types-of-visualizations/numeric-visualizations-color-and-size) pages for worked examples.

## Icon properties (points)

When a point layer should be drawn as icons instead of circles, set `iconImage` in the paint block. The icon takes on the layer's `color` and `size`. See [Icons](/felt-style-language/icons) for the full catalog and examples.

<table><thead><tr><th width="200"></th><th width="180">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>iconImage</code></td><td>string | string[]</td><td>An icon slug, or an emoji as <code>"emoji::name:"</code>. Array form assigns an icon per category/class.</td></tr><tr><td><code>iconFrame</code></td><td><code>"none"</code> | <code>"frame-circle"</code> | <code>"frame-square"</code></td><td>A frame drawn behind built-in icons (not emojis).</td></tr><tr><td><code>iconRotation</code></td><td>number | string</td><td>Rotation in degrees, or a column name to rotate by.</td></tr><tr><td><code>iconHideOnZoom</code></td><td>number</td><td>Zoom level below which icons render as plain points.</td></tr></tbody></table>

## Paint arrays (casing & layered rendering)

The `paint` property can be an **array of paint objects** for layered rendering. The layers render last-to-first (the last entry is the bottom layer, the first is on top). This is most often used for road-style casing: a wide dark outline beneath a narrower colored fill.

```json
{
  "version": "2.3.1",
  "type": "simple",
  "paint": [
    {"color": "#3B82F6", "size": 4, "opacity": 1.0, "lineCap": "round", "lineJoin": "round"},
    {"color": "#1E3A5F", "size": 8, "opacity": 1.0, "lineCap": "round", "lineJoin": "round"}
  ],
  "legend": {}
}
```

Paint arrays work on all geometries but are most useful for line casing.

## Label block reference

Label rendering is configured separately, in the `label` block. See [The label block](/felt-style-language/style-definition-blocks/the-label-block) for the full property reference, defaults, and placement-by-geometry guidance.

## Default values

<table><thead><tr><th width="271">Name</th><th>Points</th><th>Polygons</th><th>Lines</th></tr></thead><tbody><tr><td><code>color</code></td><td>"#EE4D5A"</td><td>"#826DBA"</td><td>"#4CC8A3"</td></tr><tr><td><code>highlightColor</code></td><td>"#EA3891"</td><td>"#EA3891"</td><td>"#EA3891"</td></tr><tr><td><code>highlightStrokeColor</code></td><td>"#EA3891"</td><td>"#EA3891"</td><td>"#EA3891"</td></tr><tr><td><code>dashArray</code></td><td>-</td><td>-</td><td></td></tr><tr><td><code>lineCap</code></td><td>-</td><td>-</td><td>"round"</td></tr><tr><td><code>lineJoin</code></td><td>-</td><td>-</td><td>"round"</td></tr><tr><td><code>opacity</code></td><td>0.9</td><td>0.8</td><td>1</td></tr><tr><td><code>isSandwiched</code></td><td>-</td><td>false</td><td>-</td></tr><tr><td><code>size</code></td><td>4</td><td>-</td><td>2</td></tr><tr><td><code>strokeColor</code></td><td>"#F9F8Fb"</td><td>"#777777"</td><td>-</td></tr><tr><td><code>strokeWidth</code></td><td>1</td><td>1</td><td>-</td></tr></tbody></table>


# The label block

{% hint style="info" %}
The label block defines how feature labels are rendered.
{% endhint %}

These are the properties available to define label rendering. Point and line features are labeled directly; polygon labels are anchored at each polygon's centroid (or along lines with `renderAsLines`).

|                  | Type                                                                                    | Applies to                 | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| ---------------- | --------------------------------------------------------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `color`          | string \| auto \| [Interpolator](/felt-style-language/zoom-based-styling/interpolators) | Points and lines           | Optional. The label color                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `fontFamily`     | string                                                                                  | Points and lines           | Optional. The font family to use                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `fontSize`       | number \| [Interpolator](/felt-style-language/zoom-based-styling/interpolators)         | Points and lines           | Optional. The font size in pixels                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `fontStyle`      | "normal" \| "italic"                                                                    | Points and lines           | Optional. The font style (case-insensitive). Values other than italic render as normal                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `fontWeight`     | number                                                                                  | Points and lines           | Optional. The font weight                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `haloColor`      | string \| [Interpolator](/felt-style-language/zoom-based-styling/interpolators)         | Points and lines           | Optional. The label halo color                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `haloWidth`      | number \| [Interpolator](/felt-style-language/zoom-based-styling/interpolators)         | Points and lines           | Optional. The label halo width in pixels                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `justify`        | "auto" \| "left" \| "right" \| "center"                                                 | Points and lines           | Optional. Text justification for multi-line labels                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `letterSpacing`  | number                                                                                  | Points and lines           | Optional. Horizontal spacing behaviour between text characters                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `lineHeight`     | number                                                                                  | Points and lines           | Optional. Sets the height of a line box                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `maxLineChars`   | number \| [Interpolator](/felt-style-language/zoom-based-styling/interpolators)         | Points and lines           | Optional. Defines the max number of characters before a line break                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `maxZoom`        | number                                                                                  | Points and lines           | Optional. The maximum zoom level at which the label will be shown. Defaults to 24                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `minZoom`        | number                                                                                  | Points and lines           | Optional. The minimum zoom level at which the label will be shown. Defaults to 24 (see note below)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `offset`         | \[number, number] \| number                                                             | Points and lines           | Optional. In the case of points, this value must be an array of two numeric offsets that will be applied on the positive X and Y axis defined by the label placement (i.e. an offset of \[3,4] with a label placement of NE moves the label 3pixels to the right and 4 pixels above of the anchor point. An offset of \[3,4] with a label placement of SW moves the label 3pixels to the left and 4 pixels below of the anchor point). In case of lines, this value is a single number that moves the label following the label position (i.e. an offset of 3 with a label position of Above will move the label 3 pixels above the line following the line normal. An offset of 3 with a label position of Below will mode the label 3 pixels under the line following the line normal) |
| `padding`        | number                                                                                  | Points and lines           | Optional. Adds invisible padding around the label that's used to compute label collisions                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `placement`      | string\[] \| "auto" \| string                                                           | Points and lines           | Optional. On points: an array of placements to try (`"N"`, `"NE"`, `"E"`, `"SE"`, `"S"`, `"SW"`, `"W"`, `"NW"`, `"Center"`) or `"auto"`; if all placements collide with existing labels, the label is not shown. On lines: a single placement relative to the line — `"Above"`, `"Center"` or `"Below"`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `renderAsLines`  | boolean                                                                                 | Polygons                   | Optional. Renders labels along lines instead of using the centroids                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `repeatDistance` | number                                                                                  | Lines                      | Optional. The distance in pixels between label repetitions on a line                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `textTransform`  | "none" \| "uppercase" \| "lowercase"                                                    | Points and lines           | Optional. Specifies how to capitalize the label                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `isClickable`    | boolean                                                                                 | Points, Lines and Polygons | Optional. A flag to tell if labels should be clickable                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `isHoverable`    | boolean                                                                                 | Points, Lines and Polygons | Optional. A flag to tell if labels should be hoverable                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |

In addition, lines support `maxAngle` (number, default `30`) — the maximum angle in degrees for text following a curved line.

See [default values](#default-values) of these attributes on each label type.

## Placement by geometry

`placement` is shaped differently depending on the geometry being labeled:

* **Points:** an array of compass directions to try, e.g. `["E"]` (right of the point), `["E", "W"]`, or `["Center"]`. Felt uses the first placement that doesn't collide.
* **Lines:** a single string relative to the line — `"Above"`, `"Center"`, or `"Below"`. Tune `repeatDistance` (pixels between repeated labels) and `maxAngle` for curved text.
* **Polygons:** `["Center"]` places the label at the polygon's centroid.

{% hint style="info" %}
Prefer `"color": "auto"` and `"haloColor": "auto"` so labels stay legible across themes and basemaps. See [Colors & palettes](/felt-style-language/colors-and-palettes).
{% endhint %}

If using a `categorical` or `numeric` visualization, the properties above may be arrays. If there's a single value in the array, that value is used in all categories. If there are as many values as `categories`, the corresponding value will be used for each category. You can see an example of a `categorical` viz [here](/felt-style-language/types-of-visualizations/categorical-visualizations).

## Default values

<table><thead><tr><th width="202">Name</th><th>Points</th><th>Lines</th><th>Centroids</th></tr></thead><tbody><tr><td><code>color</code></td><td>"#333333"</td><td>"#333333"</td><td>"#333333"</td></tr><tr><td><code>fontFamily</code></td><td>"Atlas Grotesk LC"</td><td>"Atlas Grotesk LC"</td><td>"Atlas Grotesk LC"</td></tr><tr><td><code>fontSize</code></td><td>13</td><td>13</td><td>13</td></tr><tr><td><code>fontStyle</code></td><td>"Normal"</td><td>"Normal"</td><td>"Normal"</td></tr><tr><td><code>fontWeight</code></td><td>500</td><td>400</td><td>500</td></tr><tr><td><code>haloColor</code></td><td>"#fbfcfb"</td><td>"#fbfcfb"</td><td>"#fbfcfb"</td></tr><tr><td><code>haloWidth</code></td><td>1</td><td>1</td><td>1</td></tr><tr><td><code>justify</code></td><td>"auto"</td><td>"auto"</td><td>"auto"</td></tr><tr><td><code>letterSpacing</code></td><td>0</td><td>0</td><td>0</td></tr><tr><td><code>lineHeight</code></td><td>1.2</td><td>1.2</td><td>1.2</td></tr><tr><td><code>maxLineChars</code></td><td>10</td><td>-</td><td>10</td></tr><tr><td><code>maxAngle</code></td><td>-</td><td>30</td><td>-</td></tr><tr><td><code>maxZoom</code></td><td>24</td><td>24</td><td>24</td></tr><tr><td><code>minZoom</code></td><td>24</td><td>24</td><td>24</td></tr><tr><td><code>offset</code></td><td>[8, 8]</td><td>0</td><td>-</td></tr><tr><td><code>padding</code></td><td>2</td><td>1</td><td>0</td></tr><tr><td><code>placement</code></td><td>"auto"</td><td>"Above"</td><td>"Center"</td></tr><tr><td><code>repeatDistance</code></td><td>-</td><td>250</td><td>-</td></tr><tr><td><code>textTransform</code></td><td>"none"</td><td>"none"</td><td>"none"</td></tr></tbody></table>

{% hint style="info" %}
The default `minZoom` and `maxZoom` are both 24 — an empty range, which is how labels are hidden by default. To show labels, set a real zoom range on the label block.
{% endhint %}


# The popup block

The popup block contains information on how the popup is displayed and which attributes to show.

These are the fields that each popup block can contain:

<table><thead><tr><th width="221">Field name</th><th>Description</th></tr></thead><tbody><tr><td><code>type</code></td><td>Optional. <code>"attributes"</code> (default), <code>"iframe"</code>, or <code>"html"</code>.</td></tr><tr><td><code>titleAttribute</code></td><td>Optional. The attribute (or attributes) used to title the popup if available.</td></tr><tr><td><code>imageAttribute</code></td><td>Optional. The attribute that will be used to populate the popup image if available.</td></tr><tr><td><code>popupLayout</code></td><td>Optional. One of <code>"table"</code> or <code>"list"</code>. The way the popup shows its contents. Defaults to <code>"table"</code>.</td></tr><tr><td><code>popupLocation</code></td><td>Optional. Where the popup appears: <code>"onMap"</code>, <code>"leftSidebar"</code>, <code>"rightSidebar"</code>, or <code>"modal"</code>.</td></tr><tr><td><code>headerLayout</code></td><td>Optional. <code>"standard"</code>, <code>"compact"</code>, or <code>"none"</code>.</td></tr><tr><td><code>keyAttributes</code></td><td>Optional. A list of attributes to show in the popup following the order defined here. If it’s not defined, only attributes with a value will show. If it’s defined, all listed attributes show even when the selected feature doesn’t include them.</td></tr><tr><td><code>url</code></td><td>Iframe popups only. The URL to embed; supports the <code>{{column_name}}</code> template syntax.</td></tr><tr><td><code>width</code></td><td><code>iframe</code> and <code>html</code> popups only. The popup width in pixels.</td></tr><tr><td><code>height</code></td><td><code>iframe</code> and <code>html</code> popups only. The popup height in pixels.</td></tr></tbody></table>

{% code title="Example of a popup block" %}

```json
"popup": {
  "titleAttribute": "name",
  "keyAttributes": ["osm_id", "highway", "ref", "place"],
  "popupLayout": "list",
  "popupLocation": "onMap",
  "headerLayout": "standard"
}
```

{% endcode %}

## Iframe and HTML popups

An `iframe` popup embeds external content. Use the template syntax `{{column_name}}` (or `{{['Column Name']}}` for names with spaces) to inject feature values into the URL or HTML.

An `html` popup renders a rich HTML template that is authored and stored with the layer in the Felt app — the FSL block only sets `"type": "html"` plus the common fields above; the template itself is not part of the style.

{% code title="Iframe popup" %}

```json
"popup": {
  "type": "iframe",
  "url": "https://example.com/details/{{id}}",
  "popupLocation": "modal",
  "width": 600,
  "height": 800
}
```

{% endcode %}

## H3 aggregation attributes

[H3](/felt-style-language/types-of-visualizations/h3) hexes show **aggregated** values rather than raw feature attributes, so their popups reference special attribute names:

* `"felt:cluster_size"` — number of features in the hex
* `"felt:sum:column"`, `"felt:mean:column"`, `"felt:min:column"`, `"felt:max:column"` — aggregates of `column`

```json
"popup": {
  "titleAttribute": "felt:sum:revenue",
  "keyAttributes": ["felt:cluster_size", "felt:mean:revenue", "felt:max:revenue"]
}
```


# The attributes block

The attributes block contains information on how attributes will be shown both on the popup and the table. Each attribute definition can contain the following properties:

<table><thead><tr><th width="191">Field name</th><th>Description</th></tr></thead><tbody><tr><td><code>displayName</code></td><td>Optional. How this attribute will be shown in different parts of the Felt UI.</td></tr><tr><td><code>format</code></td><td>Optional. A <a href="https://numbrojs.com/">numbro</a> object that encodes how numeric fields should be shown.</td></tr></tbody></table>

{% code title="Example of an attributes block" %}

```json
"attributes": {
  "faa": {
    "displayName": "FAA Code",
    "format": {
      "mantissa": 0,
      "thousandSeparated": true
    }
  },
  "wikipedia": {"displayName": "Wikipedia"}
}
```

{% endcode %}

## The `format` object

`format` accepts any valid [numbro format object](https://numbrojs.com/format.html) — common keys include `mantissa` (decimal places), `thousandSeparated`, `average` (compact notation like `1.2M`), `output` (`"percent"`, `"currency"`), and `prefix`/`postfix`. The object is validated by numbro itself, so anything numbro accepts is valid.

Felt adds one extra key on top of numbro: `unit`, for displaying measurements. Supported values: `meter`, `kilometer`, `foot`, `mile`, `square_meter`, `hectare`, `square_kilometer`, `square_foot`, `acre`, `square_mile`, and the adaptive `auto-distance` and `auto-area`.

```json
"attributes": {
  "parcel_area": {
    "displayName": "Parcel area",
    "format": {"mantissa": 1, "unit": "auto-area"}
  }
}
```


# The filters block

The filters block contains information on how the layer is being filtered before displaying. In order for a feature to be shown on the map it must evaluate the filter expression to `true`.

Filters are written using a JSON infix notation that looks like one of `[identifier, operator, operand]`, `true` or `false` .

* Valid identifiers are either a feature property or a nested expression.
* Valid operators are:
  * `"lt"` – Less than
  * `"gt"` – Greater than
  * `"le"` – Less than or equal to
  * `"ge"` – Greater than or equal to
  * `"eq"` – Equal to
  * `"ne"` – Not equal to
  * `"and"` – And, cast to boolean
  * `"or"` – Or, cast to boolean
  * `"cn"` – Contains the operand, cast to string
  * `"nc"` – Does not contain the operand, cast to string
  * `"in"` – Contained in the operand list
  * `"ni"` – Not contained in the operand list
  * `"is"` – Used to match against null values
  * `"isnt"` – Used to match against null values
* Operands are:
  * A numerical value, a string value, a boolean value
  * An array of numerical, string, or boolean values, a shorthand expanded to these patterns:
    * Input 1: `[id, "in", [element1, …, elementN]]`
    * Expansion 1: `id` is equal (`"eq"`) to one or more of the elements
    * Input 2: `[id, "ni", [element1, …, elementN]]`
    * Expansion 2: `id` is not equal (`"ne"`) to any of the elements
    * Not defined for operators other than `"in"` and `"ni"`
  * A nested expression
* In cases of type mismatch cast the identifier value to the operand’s type
  * Type casting applies element-wise to lists with `"in"` and `"ni"` operators

{% code title="Example of a filter block that filters out features with a value less than 50000 on the acres property" %}

```json
"filters": ["acres", "lt", 50000]
```

{% endcode %}

{% code title="Example of a more complex filter block" %}

```json
"filters": [["acres", "ge", 50000], "and", ["acres", "le", 70000]]
```

{% endcode %}

## Behavior notes

* **Case & diacritics:** `eq`, `ne`, `gt`, `ge`, `lt`, `le`, `cn`, and `nc` compare strings case-insensitively and diacritic-insensitively. `["status", "eq", "active"]` matches `"Active"` and `"ACTIVE"`.
* **`in` / `ni` are case-sensitive**, unlike the operators above, and do per-element type coercion (`["id", "in", [5]]` matches a string `"5"`). `in` with an empty array always returns false; `ni` with an empty array always returns true.
* **Null handling:** use `is` / `isnt` only for null/existence checks (with `null` as the value) — not for value equality. Most other operators yield null (and filter the feature out) when the column is missing or null.
* **Type coercion:** the left-hand value is cast to match the right-hand type, so `["score", "eq", 100]` matches whether the column stores `100` or `"100"`.

## Common patterns

There is no single "between" operator — combine two comparisons:

```json
"filters": [["temperature", "ge", 0], "and", ["temperature", "le", 100]]
```

Check that a value exists and is non-empty:

```json
"filters": [["name", "isnt", null], "and", ["name", "ne", ""]]
```

**Three or more conditions must be written as nested pairs, not a flat list:**

```jsonc
// WRONG: [a, "and", b, "and", c]
// RIGHT:
"filters": [["a", "eq", 1], "and", [["b", "eq", 2], "or", ["c", "eq", 3]]]
```


# Types of visualizations

The `type` field chooses how a layer's data is turned into a picture. Pick the type that matches your data and message:

| Type                                                                                          | Use it for                                                                | Works on                        |
| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------- |
| [Simple](/felt-style-language/types-of-visualizations/simple-visualizations)                  | One uniform style for every feature; raster imagery as-is                 | Points, lines, polygons, raster |
| [Categorical](/felt-style-language/types-of-visualizations/categorical-visualizations)        | Color (or icon) by a discrete category                                    | Points, lines, polygons, raster |
| [Numeric](/felt-style-language/types-of-visualizations/numeric-visualizations-color-and-size) | Vary color or size by a numeric value (choropleths, proportional symbols) | Points, lines, polygons, raster |
| [Heatmaps](/felt-style-language/types-of-visualizations/heatmaps)                             | Point density as a smooth surface                                         | Points                          |
| [H3](/felt-style-language/types-of-visualizations/h3)                                         | Aggregate points into hexagonal bins                                      | Points                          |
| [Raster](/felt-style-language/types-of-visualizations/raster)                                 | Imagery, single/multiband numeric, categorical, and hillshade             | Raster                          |

Each page lists the geometry types it applies to, the properties that matter, and worked examples. For the values you'll plug into these styles, see [Colors & palettes](/felt-style-language/colors-and-palettes), [Icons](/felt-style-language/icons), and [Classification methods](/felt-style-language/classification-methods).


# Simple visualizations

Simple visualizations are those that show each feature in a vector dataset using the same style or the image as it is in raster ones.

Simple visualizations must define `"type": "simple"` and a single value for each supported style and label properties.

### Example

The Airports layer in Felt is an example of a simple visualization using a vector dataset

![](/files/wctwlswQb42X461OVWtv)

and is defined by the following style:

```json
{
  "attributes": {
    "ele": {"displayName": "Elevation (meters)"},
    "faa": {"displayName": "FAA Code"},
    "iata": {"displayName": "IATA Code"},
    "icao": {"displayName": "ICAO Code"},
    "name": {"displayName": "Name"},
    "name_en": {"displayName": "Name (EN)"},
    "wikipedia": {"displayName": "Wikipedia entry"}
  },
  "config": {"labelAttribute": ["name_en", "name"]},
  "filters": [["name", "isnt", null], "and", ["name", "ne", ""]],
  "label": {
    "color": "hsl(40,30%,40%)",
    "fontSize": {"linear": [[12, 12], [20, 20]]},
    "fontStyle": "Normal",
    "fontWeight": 400,
    "haloColor": "hsl(40,20%,85%)",
    "haloWidth": 1.5,
    "justify": "auto",
    "letterSpacing": 0.1,
    "lineHeight": 1.2,
    "maxLineChars": 10,
    "maxZoom": 23,
    "minZoom": 10,
    "offset": [8, 0],
    "padding": 10,
    "placement": ["E", "W"]
  },
  "legend": {},
  "paint": {
    "color": "hsl(40,30%,80%)",
    "highlightColor": "#EA3891",
    "highlightStrokeColor": "#EA3891",
    "highlightStrokeWidth": {"linear": [[3, 0], [20, 2]]},
    "isSandwiched": false,
    "opacity": 1,
    "size": {"linear": [[3, 1], [20, 6]]},
    "strokeColor": "hsl(40,20%,55%)",
    "strokeWidth": {"linear": [[3, 0.5], [20, 2]]}
  },
  "version": "2.3.1"
}
```

{% hint style="info" %}
The `{"linear": [[zoom, value], …]}` objects used for `size`, `fontSize`, and `strokeWidth` above are **interpolators** — values that change with zoom level. See [Zoom-based styling](/felt-style-language/zoom-based-styling) and [Interpolators](/felt-style-language/zoom-based-styling/interpolators) for the full syntax; plain numbers work anywhere an interpolator does.
{% endhint %}

Raster layers can also use `simple` to display imagery as-is — see [Raster visualizations](/felt-style-language/types-of-visualizations/raster).

### More patterns

**Icon markers (points).** Set `iconImage` to draw points as icons; the icon takes the layer's `color` and `size`. See [Icons](/felt-style-language/icons) for the full catalog.

```json
{
  "version": "2.3.1",
  "type": "simple",
  "filters": ["status", "in", ["active"]],
  "paint": {
    "iconImage": "hospital",
    "iconFrame": "frame-circle",
    "color": "#E74C3C",
    "size": 4,
    "opacity": 0.9,
    "isClickable": true,
    "isHoverable": true
  },
  "legend": {}
}
```

**Road-style casing (lines).** A [paint array](/felt-style-language/style-definition-blocks/the-paint-block#paint-arrays-casing-and-layered-rendering) renders last-to-first, so the wider casing layer goes second (underneath) and the narrower fill goes first (on top).

```json
{
  "version": "2.3.1",
  "type": "simple",
  "config": {"labelAttribute": ["name"]},
  "paint": [
    {"color": "#FFFFFF", "size": 4, "lineCap": "round", "lineJoin": "round", "isClickable": true},
    {"color": "#374151", "size": 8, "lineCap": "round", "lineJoin": "round"}
  ],
  "label": {
    "color": "auto",
    "haloColor": "auto",
    "haloWidth": 2,
    "placement": "Above",
    "minZoom": 12
  },
  "legend": {}
}
```


# Categorical visualizations

Categorical visualizations use a categorical attribute and the categories within it to apply styling to discrete categories of the attribute. (On raster datasets, categories are raw pixel values instead — see [Raster visualizations](/felt-style-language/types-of-visualizations/raster).)

Categorical visualizations are defined using `"type": "categorical"` and, for every supported style and label property used, either a single value that will apply to all categories or an array of different values for each category.

You can list the `categories` explicitly, or use a shortcut when you don't know the exact values: `{"type": "top", "count": N}` keeps the N most common values (vector), and `{"type": "all"}` covers every unique pixel value (raster). When `showOther: true` is combined with an **explicit** `categories` array, paint/label arrays need **N + 1** values — the extra one styles the "Other" bucket. For colors, prefer a [`@palette`](/felt-style-language/colors-and-palettes) shortcut over a hand-built array.

### Example

The Global Power Plants layer in Felt is an example of a categorical layer on a vector dataset

![](/files/DK11QRpkbreaYa33GkZ5)

and is defined by the following style

```json
{
  "version": "2.3.1",
  "type": "categorical",
  "config": {
    "categoricalAttribute": "primary_fuel",
    "categories": ["Solar", "Hydro", "Wind", "Gas", "Coal", "Oil", "Nuclear"],
    "showOther": false
  },
  "legend": {"displayName": {}},
  "attributes": {
    "capacity_mw": {"displayName": "Capacity (MW)"},
    "name": {"displayName": "Name"},
    "primary_fuel": {"displayName": "Primary Fuel"}
  },
  "paint": {
    "color": [
      "#E5C550",
      "#7AB6C2",
      "#AB71A4",
      "#CC615C",
      "#AD7B68",
      "#EB9360",
      "#DEA145"
    ],
    "isSandwiched": false,
    "opacity": 1,
    "size": [{"linear": [[3, 1.5], [20, 8]]}]
  }
}
```

Notice that we are saying that the `primary_fuel` data attribute will be used to categorize elements and that the possible values of that attribute that we are interested in are `"Solar"`, `"Hydro"`, `"Wind"`, `"Gas"`, `"Coal"`, `"Oil"` and `"Nuclear"` (colors are assigned in the same order). Also notice that we are defining either a single value that will apply to all categories (i.e. `size`) or a value for each category (i.e. `color`)

### More patterns

**Palette shortcut + "top N".** When you don't know the exact category values, let Felt pick the most common ones and color them from a categorical palette:

```json
{
  "version": "2.3.1",
  "type": "categorical",
  "config": {
    "categoricalAttribute": "fuel_type",
    "categories": {"type": "top", "count": 10},
    "showOther": true
  },
  "paint": {
    "color": "@catPalette4",
    "size": 4,
    "strokeColor": "auto",
    "strokeWidth": 1,
    "opacity": 0.9,
    "isClickable": true
  },
  "legend": {"displayName": "auto"}
}
```

**A different icon per category.** `iconImage` and the paint arrays follow the same N (or N + 1 with `showOther`) pattern as `color`:

```json
{
  "version": "2.3.1",
  "type": "categorical",
  "config": {
    "categoricalAttribute": "facility_type",
    "categories": ["hospital", "school", "fire_station"],
    "showOther": true
  },
  "paint": {
    "iconImage": ["hospital", "school", "fire", "dot"],
    "iconFrame": "frame-circle",
    "color": ["#E74C3C", "#3498DB", "#E67E22", "#95A5A6"],
    "size": 4,
    "opacity": 0.9,
    "isClickable": true
  },
  "legend": {
    "displayName": {
      "hospital": "Hospitals",
      "school": "Schools",
      "fire_station": "Fire Stations"
    }
  }
}
```

**Styling "Other" separately.** With `showOther: true`, the trailing array element targets the "Other" bucket — here it is made smaller and more transparent than the named categories:

```json
{
  "version": "2.3.1",
  "type": "categorical",
  "config": {
    "categoricalAttribute": "status",
    "categories": ["active", "pending", "closed"],
    "showOther": true
  },
  "paint": {
    "color": ["#22C55E", "#F59E0B", "#EF4444", "#9CA3AF"],
    "size": [8, 8, 8, 4],
    "opacity": [0.9, 0.9, 0.9, 0.5],
    "strokeColor": "auto",
    "strokeWidth": 1
  },
  "legend": {"displayName": "auto"}
}
```

{% hint style="info" %}
Lines support color variation per category but not per-category icons. For categorical **polygons**, color is the only visual variable — keep to 5–7 categories so large filled areas stay distinguishable. Lines can still use a [paint array](/felt-style-language/style-definition-blocks/the-paint-block#paint-arrays-casing-and-layered-rendering) for casing under each colored category.
{% endhint %}


# Numeric visualizations (color & size)

Numeric visualizations use a numeric attribute to either vary colors or sizes between ranges of values and are defined in FSL with `"type": "numeric"`.

{% hint style="info" %}
Vary **color** *or* **size**, not both at once — pick the single visual variable that best carries your message. Color reads as a choropleth; size reads as proportional symbols ("bigger = more"). Polygons support color only.
{% endhint %}

Ranges are calculated across discrete steps using a classification method, or on a continuous scale between an attribute’s min and max values. See [Classification methods](/felt-style-language/classification-methods) for the full list (jenks, quantiles, equal-intervals, stddev, geo-intervals, continuous) and how to express `steps`.

#### **Color**

Color `numeric` values in your data using the `color` property.

**Stepped color**

This map shows the percent of renter occupied housing units by US county. Each county is colored according to the step of ranges it falls into using a sequential color palette where light colors are assigned to low values and darker colors for high values.

![Map link: Percent renter occupied housing by county](/files/gSpDmSsTRydhufstabFB)

The style for this map is defined

* with the visualization `type: numeric`
* `numericAttribute: "Renter occupied (%)"` and the computed `steps` using Jenks Natural Breaks over 5 classes
* the sequential `@galaxy` palette, which Felt expands to one color per class — light colors for low values, dark for high

```json
{
  "version": "2.3.1",
  "type": "numeric",
  "config": {
    "numericAttribute": "Renter occupied (%)",
    "steps": {"type": "jenks", "count": 5}
  },
  "paint": {
    "color": "@galaxy",
    "opacity": 0.9,
    "strokeColor": "auto",
    "strokeWidth": 0.5,
    "isSandwiched": true
  },
  "legend": {"displayName": "auto"}
}
```

**Continuous Color**

The map below shows average accumulated precipitation in the State of California between the years 1900 - 1960 and is colored along a continuous range between the min and max of the precipitation values.

![Map link: Average annual precipitation](/files/w4X6jCav1RrMyUMtb6b2)

In this case, the numeric style is applied using a continuous method.

* In the `config` block, `numericAttribute: PRECIP` uses the `{"type": "continuous"}` steps shortcut, so values scale across the attribute's full min–max range
* The `color` property uses the sequential `@purpYl` palette, whose colors are interpolated to give each precipitation value a unique color

```json
{
  "version": "2.3.1",
  "type": "numeric",
  "config": {
    "numericAttribute": "PRECIP",
    "steps": {"type": "continuous"}
  },
  "paint": {
    "color": "@purpYl",
    "opacity": 0.9,
    "strokeColor": "auto",
    "strokeWidth": 0,
    "isClickable": false,
    "isSandwiched": true
  },
  "legend": {"displayName": "auto"}
}
```

{% hint style="info" %}
*Any time there are fewer colors than values in a numeric style, they are interpolated in the (*[*hcl color space*](https://en.wikipedia.org/wiki/HCL_color_space)*).*
{% endhint %}

Numeric color also works on raster datasets, using `band` instead of `numericAttribute` — see [Raster visualizations](/felt-style-language/types-of-visualizations/raster).

#### Size

Size `numeric` values in your point or line data using the `size` property.

**Stepped size**

The map below shows earthquakes over the past year sized with 5 manually defined steps.

![](/files/7XOY3rNidIYKIMtYW2U8)

In this case, the numeric style is applied using a classed method.

* In the `config` block, the `numericAttribute: mag` has manually-defined `steps` for 5 classes
* The `size` property has an array of five sizes, one for each defined class

```json
{
  "version": "2.3.1",
  "type": "numeric",
  "config": {"numericAttribute": "mag", "steps": [4.5, 5.5, 6, 7, 7.5, 8.2]},
  "legend": {"displayName": "auto"},
  "paint": {
    "size": [5, 10, 12, 14, 16],
    "color": "hsl(0, 13%, 45%)",
    "opacity": 0.9,
    "strokeColor": "hsl(0, 13%, 88%)",
    "strokeWidth": 1.5
  }
}
```

*Map link:* [*Earthquakes Stepped Size*](https://felt.com/map/FSL-doc-Earthquakes-Stepped-Size-CQriP6TDT1enA5G6uNhU9CB?lat=-7.797738\&lon=-64.51902\&zoom=4)

**Continuous size**

The map below shows tonnes of corn that have been exported from Ukraine since August 2022 under the [UN’s Black Sea Grain Initiative](https://www.un.org/en/black-sea-grain-initiative). The symbol size for each country is proportionate to its value in the data.

![Map link: Black Sea Grain Initiative](/files/kDhnjDUGyMVhMYnR2tun)

To do this the min and max `steps` from the `numericAttribute: tonnes` are interpolated to be proportionately sized between a min and max point size — `size:[5,48]`

```json
{
  "version": "2.3.1",
  "type": "numeric",
  "config": {
    "steps": [33000, 2344684],
    "numericAttribute": "tonnes",
    "labelAttribute": ["category"]
  },
  "label": {
    "minZoom": 1,
    "color": "#5a5a5a",
    "fontSize": 14,
    "fontStyle": "Normal",
    "fontWeight": 500,
    "haloColor": "#d0d0d0",
    "haloWidth": 1.5,
    "offset": [8, 0]
  },
  "legend": {"displayName": {"0": "2.34M", "1": "714.65K", "2": "33K"}},
  "paint": {
    "size": [5, 48],
    "color": "hsl(22, 78%, 65%)",
    "opacity": 0.9,
    "strokeColor": "hsl(22, 78%, 88%)",
    "strokeWidth": 1.5
  }
}
```

#### Notes on `steps`, `color`, and `size`

* **Let Felt compute breaks** when you don't have the data in front of you: `"steps": {"type": "jenks", "count": 5}`. Provide explicit breaks (`[0, 3, 5, 7, 9]`) when you know the values you want. See [Classification methods](/felt-style-language/classification-methods).
* **Classed arrays** carry one value per class. With `N` break points you get `N − 1` classes, so explicit `color`/`size` arrays need `N − 1` entries.
* **Continuous `size`** is written as `[min, max]` (e.g. `[3, 30]`) and scales smoothly; proportional-symbol sizes typically range 3–30 px for points. On **lines**, `size` is width in pixels (typically 1–20 px), not a radius.
* **Continuous size legends** use inverted indices for historical reasons: `"0"` labels the maximum value, `"1"` the midpoint, and `"2"` the minimum (as in the example above). Continuous **color** legends run the intuitive way: `"0"` is the minimum. See [Legends](/felt-style-language/legends).
* **Icons can vary by value too** — set `iconImage` and let `size` (or `color`) carry the numeric variable:

```json
{
  "version": "2.3.1",
  "type": "numeric",
  "config": {"numericAttribute": "beds", "steps": {"type": "jenks", "count": 4}},
  "paint": {
    "iconImage": "hospital",
    "iconFrame": "frame-circle",
    "color": "#E74C3C",
    "size": [3, 4, 5, 6],
    "opacity": 0.9,
    "isClickable": true
  },
  "legend": {"displayName": "auto"}
}
```


# Heatmaps

Heatmaps are used to visualize the density of points on a map.

Heatmaps work on point layers only, and show concentration rather than individual feature values — if you need to read a specific attribute, use a [numeric](/felt-style-language/types-of-visualizations/numeric-visualizations-color-and-size) visualization instead. The `config` block must be empty (`{}`), and heatmaps support no popups or labels.

Heatmap visualizations are defined using `"type": "heatmap"` and allow the following properties to be set:

| Field name  | Type   | Default     | Typical range | Description                                                                                                     |
| ----------- | ------ | ----------- | ------------- | --------------------------------------------------------------------------------------------------------------- |
| `color`     | string | `"@geyser"` | —             | A [heatmap palette](/felt-style-language/colors-and-palettes#heatmap-palettes) name, from less density to more. |
| `size`      | number | 10          | 1–30 px       | Radius of influence in pixels. Larger = smoother, smaller = more detailed.                                      |
| `intensity` | number | 0.5         | 0.1–3.0       | How quickly density saturates. Higher = more contrast.                                                          |
| `opacity`   | number | 0.9         | 0–1           | Transparency.                                                                                                   |

Tune `size` for the zoom you expect to view at — larger at low zoom, smaller at high zoom.

This is an example of a heatmap visualization

![](/files/C2vt8DBqzgRiZC3rorOO)

defined with the following visualization

```json
{
  "version": "2.3.1",
  "type": "heatmap",
  "config": {},
  "legend": {"displayName": {"0": "Low", "1": "High"}},
  "paint": {"color": "@purpYlPink", "size": 10, "intensity": 0.2}
}
```


# H3

H3 visualization is a way to aggregate point data into a grid of H3 cells.

H3 visualizations are defined using `"type": "h3"` . They generally share the same properties and behaviors as [color range visualizations for polygons](/felt-style-language/types-of-visualizations/numeric-visualizations-color-and-size). Properties of specific relevance to H3 are:

| Field name         | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `aggregation`      | The aggregation method that will be used on points within each cell. Supported values are `count` (default), `sum`, `min`, `max`, and `mean` .                                                                                                                                                                                                                                                                                                                        |
| `binMode`          | `fixed` (default), `low`, `medium`, `high`, or `auto`. `fixed` keeps the cell resolution constant at all zooms; the other modes pick a resolution from the current zoom, where `low` produces larger hexagons and `high` smaller ones (`auto` behaves like `medium`).                                                                                                                                                                                                 |
| `baseBinLevel`     | Required. If `binMode` is `fixed`, this is the [H3 cell resolution](https://h3geo.org/docs/core-library/restable/) that the map will use. H3 cells vary from resolution 0 (largest) to resolution 15 (smallest). In the zoom-adaptive modes, this is the reference resolution used for calculating class breaks — you will get best results choosing a resolution that matches the `fixed` resolution you would choose for the most commonly-viewed zoom of your map. |
| `numericAttribute` | The numeric column to aggregate. Required unless `aggregation` is `count` , in which case the column choice is irrelevant.                                                                                                                                                                                                                                                                                                                                            |

This is an example of an H3 visualization

<figure><img src="/files/GBYIs7jN2MHXVpOpSYnk" alt=""><figcaption></figcaption></figure>

defined with the following style

```json
{
  "config": {
    "steps": {"type": "quantiles", "count": 5},
    "aggregation": "sum",
    "binMode": "fixed",
    "baseBinLevel": 3,
    "numericAttribute": "capacity_mw"
  },
  "paint": {"color": "@riverine"},
  "type": "h3",
  "version": "2.3.1",
  "label": {},
  "legend": {"displayName": "auto"}
}
```

`aggregation: "count"` doesn't need a `numericAttribute` — it counts the points in each cell:

```json
{
  "version": "2.3.1",
  "type": "h3",
  "config": {
    "aggregation": "count",
    "baseBinLevel": 4,
    "binMode": "medium",
    "steps": {"type": "continuous"}
  },
  "paint": {"color": "@galaxy", "opacity": 0.85, "strokeColor": "auto", "strokeWidth": 1},
  "legend": {"displayName": "auto"}
}
```

### Aggregation popups

H3 cells display **aggregated** values, not raw feature attributes. Their popups reference special attribute names — `"felt:cluster_size"` for the feature count, and `"felt:sum:column"`, `"felt:mean:column"`, `"felt:min:column"`, `"felt:max:column"` for aggregates. If no `popup` is provided, Felt auto-generates sensible stats from the aggregation type. See [the popup block](/felt-style-language/style-definition-blocks/the-popup-block#h3-aggregation-attributes).

```json
{
  "version": "2.3.1",
  "type": "h3",
  "config": {
    "numericAttribute": "revenue",
    "aggregation": "sum",
    "baseBinLevel": 5,
    "binMode": "medium",
    "steps": {"type": "jenks", "count": 5}
  },
  "paint": {"color": "@copper", "opacity": 0.85, "strokeColor": "auto", "strokeWidth": 1, "isClickable": true},
  "popup": {
    "titleAttribute": "felt:sum:revenue",
    "keyAttributes": ["felt:cluster_size", "felt:mean:revenue", "felt:min:revenue", "felt:max:revenue"]
  },
  "attributes": {"revenue": {"displayName": "Revenue ($)", "format": {"thousandSeparated": true, "mantissa": 0}}},
  "legend": {"displayName": "auto"}
}
```

{% hint style="info" %}
H3 cells are rendered as polygons, so `size` has no effect on them — control cell size with `baseBinLevel` (H3 resolution) and `binMode` (how that resolution adapts to zoom). The `attributes` block renames the **source column**, so `"revenue"` makes a popup entry read "Sum of Revenue ($)".
{% endhint %}


# Raster visualizations

Raster layers are styled with one of five `type` values. They share a common set of `config` options and **never support popups or labels**.

| Mode                                                                      | `type`        | Use it for                                                     |
| ------------------------------------------------------------------------- | ------------- | -------------------------------------------------------------- |
| [Image](#image-simple)                                                    | `simple`      | Display the raster as-is (satellite, aerial, base tiles)       |
| [Numeric — single band](#numeric-single-band)                             | `numeric`     | Classify one band (elevation, temperature, a hazard index)     |
| [Numeric — multiband / raster algebra](#numeric-multiband-raster-algebra) | `numeric`     | Compute a spectral index (NDVI, NDMI, NDWI) from several bands |
| [Categorical](#categorical)                                               | `categorical` | Discrete pixel classes (land cover, soil types)                |
| [Hillshade](#hillshade)                                                   | `hillshade`   | Relief shading from elevation, optionally tinted               |

## Shared `config` options

| Option             | Notes                                                                                                                                                                                                   |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `band`             | Which band to read (1-indexed). Numeric/hillshade.                                                                                                                                                      |
| `steps`            | Classification — see [Classification methods](/felt-style-language/classification-methods). `{"type": "continuous"}` for smooth, or breaks like `[0, 500, 1000]`.                                       |
| `method`           | Spectral-index formula and band mapping (multiband only).                                                                                                                                               |
| `categories`       | Categorical only — `{"type": "all"}` for every pixel value, or an explicit array.                                                                                                                       |
| `noData`           | Value(s) to exclude, e.g. `[-9999]` for voids or `[0]` for no-data.                                                                                                                                     |
| `rasterResampling` | Numeric and tinted hillshade only — categorical rasters reject it (they always resample nearest). `"nearest"` keeps exact pixel values; `"linear"` interpolates (use for continuous imagery/elevation). |
| `outOfRangeValues` | `"color"` clamps to the nearest class color; `"hide"` makes them transparent.                                                                                                                           |

In `paint`, `color` takes a [raster palette](/felt-style-language/colors-and-palettes#raster-palettes) or an explicit color array, plus `opacity` and `isSandwiched` (render below basemap water/roads).

## Image (simple)

Renders the image with no classification — just `opacity` and resampling.

```json
{
  "version": "2.3.1",
  "type": "simple",
  "config": {},
  "paint": {"opacity": 0.93, "isSandwiched": false}
}
```

## Numeric — single band

Classify one band. Prefer `continuous` for smooth data (elevation, temperature); use breaks for discrete groups (hazard levels).

```json
{
  "version": "2.3.1",
  "type": "numeric",
  "config": {
    "band": 1,
    "steps": {"type": "continuous"},
    "noData": [-9999],
    "rasterResampling": "linear"
  },
  "paint": {"color": "@mplVirdis", "opacity": 1, "isSandwiched": false},
  "legend": {"displayName": "auto"}
}
```

A style is **classed** when `steps` has more than two break points — the color array then needs one entry per class (`N` break points produce `N − 1` classes; extra colors are ignored). With exactly two steps (`[min, max]`, as below), the style is **continuous**: the whole color array is interpolated smoothly across the range:

```json
{
  "version": "2.3.1",
  "type": "numeric",
  "config": {"band": 1, "steps": [-154.46, 7987.46]},
  "legend": {"displayName": {"0": "-154.46", "1": "7.99K"}},
  "paint": {
    "opacity": 1,
    "isSandwiched": false,
    "color": ["#454b9f", "#2d79a4", "#18a2a9", "#8cc187", "#e5d96c", "#eab459", "#ef8b45", "#e66250", "#db2d5e"]
  }
}
```

## Numeric — multiband (raster algebra)

Compute a derived index from multiple bands before classifying. The `config.method` names the index and maps its inputs to band numbers, and `band` lists every band used. Supported indices:

* **NDVI** — `(NIR − R) / (NIR + R)`, vegetation health. Requires `NIR`, `R`.
* **NDMI** — `(NIR − SWIR) / (NIR + SWIR)`, moisture. Requires `NIR`, `SWIR`.
* **NDWI** — `(G − NIR) / (G + NIR)`, water. Requires `G`, `NIR`.

Band numbers are 1-indexed and sensor-specific (Landsat 8/9: R=4, G=3, NIR=5, SWIR=6 · Sentinel-2: R=4, G=3, NIR=8, SWIR=11).

```json
{
  "version": "2.3.1",
  "type": "numeric",
  "config": {
    "method": {"NDVI": {"NIR": 5, "R": 4}},
    "band": [5, 4],
    "steps": {"type": "continuous"},
    "noData": [0],
    "rasterResampling": "nearest",
    "outOfRangeValues": "hide"
  },
  "paint": {"color": "@cbRedYlGrn", "opacity": 1, "isSandwiched": false},
  "legend": {"displayName": "auto"}
}
```

NDVI output ranges from −1 (water/bare) to 1 (dense vegetation); classify it with explicit breaks (e.g. `[-1, -0.2, 0, 0.2, 0.4, 0.6, 1]`) for labelled classes. Good starting points: NDVI → `[-0.5, 0.5]` with `@cbRedYlGrn`; NDWI → `[-0.5, 0.25]` and NDMI → `[-0.5, 0.5]` with `@feltVibrant`.

## Categorical

Raster categories are **raw pixel values** (integers), so there is no `categoricalAttribute`. Use `{"type": "all"}` when you don't know the values, or an explicit array when you do, with one color per value. Don't set `rasterResampling` here — categorical rasters always use nearest resampling automatically (the property is rejected on this type), since linear interpolation would blend category codes into invalid values.

```json
{
  "version": "2.3.1",
  "type": "categorical",
  "config": {"categories": [1, 2, 3, 4, 5]},
  "paint": {
    "color": ["#228B22", "#4682B4", "#DAA520", "#8B4513", "#808080"],
    "opacity": 0.85,
    "isSandwiched": true
  },
  "legend": {
    "displayName": {"1": "Forest", "2": "Water", "3": "Cropland", "4": "Bare Soil", "5": "Urban"}
  }
}
```

For raster categorical, use the [vector categorical palettes](/felt-style-language/colors-and-palettes#categorical-distinct-colors) (`@catPalette1`…`@catPalette7`). A fully-transparent class can be expressed as `"rgba(0, 0, 0, 0)"`.

## Hillshade

Simulates light and shadow on terrain. **Plain** hillshade is grayscale relief (`config` carries just `band`; `paint` carries `source` and `intensity`). **Tinted** hillshade overlays a hypsometric color ramp — add `steps` to `config` and `color` to `paint`; only do this when elevation coloring is wanted.

| Paint property | Default | Notes                                                                         |
| -------------- | ------- | ----------------------------------------------------------------------------- |
| `source`       | 315     | Light azimuth in degrees (0 = North, 90 = East). 315 (northwest) is standard. |
| `intensity`    | 0.5     | Shadow intensity 0–1; higher = more dramatic relief.                          |
| `color`        | —       | Raster palette or color array (tinted only), low → high elevation.            |

```json
{
  "version": "2.3.1",
  "type": "hillshade",
  "config": {"band": 1},
  "paint": {"source": 315, "intensity": 0.5, "isSandwiched": true},
  "legend": {}
}
```

Tinted, classifying elevation like any numeric raster (use `rasterResampling: "linear"` and `noData` for voids):

```json
{
  "version": "2.3.1",
  "type": "hillshade",
  "config": {
    "band": 1,
    "steps": {"type": "quantiles", "count": 6},
    "noData": [-9999],
    "rasterResampling": "linear"
  },
  "paint": {"color": "@terrain", "source": 315, "intensity": 0.5, "isSandwiched": true},
  "legend": {"displayName": "auto"}
}
```


# Colors & palettes

Colors appear throughout the Felt Style Language — in `paint` (`color`, `strokeColor`), in `label` (`color`, `haloColor`), and in legends. There are three ways to express a color:

1. **A literal color** — a hex, HSL, or RGB string.
2. **A smart color** — the keyword `"auto"`, which Felt resolves to a contrasting color at render time.
3. **A palette shortcut** — a named, multi-color ramp like `@galaxy`, used by data-driven (categorical, numeric, heatmap, H3) visualizations.

## Literal colors

Any of these string formats are accepted wherever a single color is expected:

```json
{
  "color": "#3B82F6",
  "strokeColor": "hsl(217, 80%, 40%)",
  "haloColor": "rgb(38, 113, 0)"
}
```

`rgba(...)` is also supported and is handy for fully-transparent fills (for example, a transparent "Other"/background class in a categorical raster: `"rgba(0, 0, 0, 0)"`).

## Smart color: `"auto"`

`"auto"` computes a contrasting color automatically, based on the current map theme and the colors around it. It keeps strokes and label halos legible without hard-coding a value that might clash on a dark basemap or against a particular fill.

Prefer `"auto"` for:

* `strokeColor` on points and polygons
* `color` and `haloColor` on labels

…unless a specific color has been requested.

```json
{
  "paint": {
    "color": "#8F7EBF",
    "strokeColor": "auto",
    "strokeWidth": 1
  },
  "label": {
    "color": "auto",
    "haloColor": "auto",
    "haloWidth": 1.5
  }
}
```

## Palette shortcuts

For any **data-driven** visualization, prefer a named palette over a hand-built color array. Palettes are referenced with an `@` prefix (for example `"color": "@galaxy"`) and Felt expands them to the right number of colors for your classes or categories.

{% hint style="info" %}
Only the palette names listed below are valid. Use **sequential** palettes for single-direction data (population, elevation), and **diverging** palettes for data with a meaningful midpoint (change from a baseline, temperature anomaly). Reserve literal hex/HSL colors for simple (uniform) styles or when a specific color is requested.
{% endhint %}

### Vector palettes

Used by categorical, numeric, heatmap, and H3 visualizations on points, lines, and polygons.

#### Sequential (low → high)

| Palette      | Description                        |
| ------------ | ---------------------------------- |
| `@galaxy`    | Purple to yellow (popular default) |
| `@ylRed`     | Yellow to red                      |
| `@ylGrn`     | Yellow to green                    |
| `@purpYl`    | Purple to yellow                   |
| `@pinkYl`    | Pink to yellow                     |
| `@lightning` | Lightning gradient                 |
| `@copper`    | Copper gradient                    |
| `@spruce`    | Spruce green                       |
| `@riverine`  | Water gradient                     |
| `@neptune`   | Blue gradient                      |
| `@violet`    | Violet gradient                    |
| `@purple`    | Purple gradient                    |

#### Diverging (two directions from a center)

| Palette   | Description                      |
| --------- | -------------------------------- |
| `@bluRd`  | Blue to red                      |
| `@tealOr` | Teal to orange (colorblind-safe) |
| `@bluBr`  | Blue to brown                    |
| `@grnOr`  | Green to orange                  |
| `@purGrn` | Purple to green                  |
| `@weath`  | Weather gradient                 |

#### Categorical (distinct colors)

| Palette                         | Description                    |
| ------------------------------- | ------------------------------ |
| `@catPalette1` … `@catPalette7` | Categorical color schemes      |
| `@catPalettePT1`                | Paul Tol categorical palette 1 |
| `@catPalettePT2`                | Paul Tol categorical palette 2 |

Sequential and diverging vector palettes also come in numbered variants that request a specific number of colors — for example `@galaxy7`, `@galaxy8`, `@galaxy9`. Variants exist for 7, 8, and 9 colors (`@lightning` also has a 6-color variant). The unnumbered name lets Felt pick the right count for you, which is usually what you want.

### Heatmap palettes

Used by `heatmap` visualizations, ordered from least to most density.

| Palette          | Description                                |
| ---------------- | ------------------------------------------ |
| `@geyser`        | Green → beige → orange → red (default)     |
| `@redOrHeat`     | Yellow → orange → red → magenta            |
| `@purpYlHeat`    | Light yellow → coral → pink → purple       |
| `@lightningHeat` | Purple → teal → green → yellow             |
| `@ylGrnHeat`     | Dark teal → green → yellow                 |
| `@bluRdHeat`     | Blue → gray → red (diverging feel)         |
| `@tealRedHeat`   | Teal → green → yellow → orange → pink      |
| `@purpYlPink`    | Purple → teal → yellow → orange → red/pink |

### Raster palettes

Used by numeric, hillshade, and raster-algebra visualizations.

#### Sequential

| Palette          | Description                                                                           |
| ---------------- | ------------------------------------------------------------------------------------- |
| `@feltGrays`     | Grayscale gradient                                                                    |
| `@cbBlues`       | ColorBrewer blues                                                                     |
| `@cbPurples`     | ColorBrewer purples                                                                   |
| `@feltPinks`     | Pink gradient                                                                         |
| `@cmOceanGreens` | Ocean greens gradient                                                                 |
| `@pattFeltOcean` | Teal ocean gradient                                                                   |
| `@cmOceanDeep`   | Deep ocean gradient                                                                   |
| `@mpaInferno`    | Matplotlib inferno — note the spelling: `mpa`, not `mpl`                              |
| `@mplPlasma`     | Matplotlib plasma                                                                     |
| `@mplVirdis`     | Matplotlib viridis, perceptually uniform — note the spelling: `Virdis`, not `Viridis` |
| `@cividis`       | Cividis — colorblind-safe                                                             |
| `@feltYlRed`     | Felt yellow to red                                                                    |
| `@veg`           | Vegetation gradient                                                                   |

#### Diverging

| Palette        | Description                            |
| -------------- | -------------------------------------- |
| `@feltWeath`   | Felt weather gradient                  |
| `@feltHeat`    | Felt heat gradient                     |
| `@feltVibrant` | Felt vibrant gradient                  |
| `@nclBlOrRed`  | NCL blue-orange-red diverging          |
| `@cbRedYlGrn`  | ColorBrewer red-yellow-green diverging |
| `@cbBrBG`      | ColorBrewer brown-blue-green diverging |

#### Terrain

| Palette        | Description                |
| -------------- | -------------------------- |
| `@terrain`     | Terrain elevation gradient |
| `@rTerrain`    | R terrain gradient         |
| `@feltHypso`   | Felt hypsometric tints     |
| `@wikiTerrain` | Wikipedia terrain gradient |

#### Vegetation

| Palette     | Description                |
| ----------- | -------------------------- |
| `@nasaNDVI` | NASA NDVI vegetation index |

For raster **categorical** layers, use the vector categorical palettes above (`@catPalette1` … `@catPalette7`, `@catPalettePT1`, `@catPalettePT2`).

Raster palettes also support numbered variants to request a specific number of steps — for example `@feltGrays2`, `@feltGrays3`, … `@feltGrays9`.

## How colors map to classes

When a palette or color array drives a data-driven visualization, the number of colors should line up with the number of classes or categories:

* **Categorical:** one color per category. With `showOther: true` and an explicit `categories` array, provide **N + 1** colors — the extra one styles the "Other" bucket.
* **Classed numeric:** **N break points produce N − 1 classes**, so an explicit color array needs N − 1 entries.
* **Continuous:** provide **2 or more** colors and Felt interpolates smoothly between them in the [HCL color space](https://en.wikipedia.org/wiki/HCL_color_space).

A palette shortcut handles all of this for you — Felt expands `@galaxy` to exactly the colors it needs. Reach for explicit arrays only when you want precise control over each color.


# Icons

Point layers can be drawn as icons instead of plain circles. Set `iconImage` in the `paint` block to a built-in icon slug (or an emoji), and the icon takes on the layer's `color` and `size`.

Icons work in [simple](/felt-style-language/types-of-visualizations/simple-visualizations), [categorical](/felt-style-language/types-of-visualizations/categorical-visualizations), and [numeric](/felt-style-language/types-of-visualizations/numeric-visualizations-color-and-size) point visualizations. In categorical and numeric styles, `iconImage` (and the other paint properties) may be an array so each category or class gets its own marker.

## Icon paint properties

| Property         | Type                | Description                                                                        |
| ---------------- | ------------------- | ---------------------------------------------------------------------------------- |
| `iconImage`      | string \| string\[] | An icon slug from the catalog below, or an emoji in the form `"emoji::name:"`.     |
| `iconFrame`      | string              | `"none"`, `"frame-circle"`, or `"frame-square"`. Built-in icons only (not emojis). |
| `iconRotation`   | number \| string    | Rotation in degrees, or the name of a column to rotate by.                         |
| `iconHideOnZoom` | number              | Zoom level below which icons are drawn as plain points instead. Optional.          |

When `iconImage` is set, `color` fills the icon and `size` scales it (the same `size` you would use for a circle radius).

## Examples

A single icon marker, framed in a circle:

```json
{
  "version": "2.3.1",
  "type": "simple",
  "paint": {
    "iconImage": "hospital",
    "iconFrame": "frame-circle",
    "color": "#E74C3C",
    "size": 4,
    "opacity": 0.9,
    "isClickable": true
  },
  "legend": {}
}
```

To give each category its own icon, make `iconImage` an array — see the icon-per-category example in [Categorical visualizations](/felt-style-language/types-of-visualizations/categorical-visualizations).

## Available icons

{% hint style="warning" %}
Use only the icon slugs listed here. Invented names will not render.
{% endhint %}

* **Shapes**: `dot`, `square`, `diamond`, `triangle`, `x`, `plus`, `circle-line`, `circle-slash`, `circle-triangle`, `circle-x`, `circle-plus`, `star`, `heart`, `hexagon`, `octagon`
* **Arrows & directions**: `arrow-up`, `chevron-up`, `double-chevron-up`, `direction-up` (combine with `iconRotation` to point them anywhere)
* **Transportation**: `pedestrian`, `bicycle`, `wheelchair`, `airport`, `car`, `bus`, `train`, `truck`, `ferry`, `sailboat`, `electric-service`, `gas-service`, `blood-clinic`, `badge`, `traffic-light`, `traffic-cone`, `road-sign-caution`
* **Activities & places**: `person`, `restroom`, `house`, `work`, `letter`, `hotel`, `factory`, `hospital`, `religious-facility`, `school`, `government`, `university`, `bank`, `landmark`, `museum`, `clothing`, `shopping`, `store`, `bar`, `pub`, `cafe`, `food`, `park`, `amusement-park`, `camping-tent`, `cabin`, `picnic`, `water-refill`, `trailhead`, `guidepost`, `viewpoint`, `camera`, `us-football`, `football`, `tennis`, `binoculars`, `swimming`
* **Infrastructure**: `zap`, `battery-full`, `battery-half`, `battery-low`, `boom`, `radar`, `wind-turbine`, `solar-panel`, `antenna`, `telephone-pole`, `oil-well`, `oil-barrel`, `railroad-track`, `bridge`, `lighthouse`, `lock-closed`, `lock-open`, `wifi`, `trash`, `recycle`
* **Nature**: `tree`, `flower`, `leaf`, `fire`, `mountain`, `snowy-mountain`, `volcano`, `island`, `wave`, `hot-springs`, `water`, `lake`, `ocean`, `animal`, `bird`, `duck`, `dog`, `fish`, `beach`, `wetland`
* **Weather**: `sun`, `moon`, `cloud`, `partial-sun`, `rain`, `lightning`, `snowflake`, `wind`, `snow`, `fog`, `sleet`, `hurricane`
* **Signs**: `warning`, `parking`, `info`, `circle-exclamation`

## Emojis

To use an emoji as a marker, write it as `"emoji::name:"` — note the double colon after `emoji` and the trailing colon. For example: `"emoji::fire:"`, `"emoji::tree:"`. `iconFrame` does not apply to emojis. Prefer built-in icons unless an emoji is specifically requested.

```json
{
  "version": "2.3.1",
  "type": "simple",
  "paint": {
    "iconImage": "emoji::fire:",
    "size": 6
  },
  "legend": {}
}
```


# Classification methods

[Numeric](/felt-style-language/types-of-visualizations/numeric-visualizations-color-and-size), [H3](/felt-style-language/types-of-visualizations/h3), and [raster](/felt-style-language/types-of-visualizations/raster) (numeric and tinted hillshade) visualizations all turn a range of values into discrete classes (or a smooth gradient) using the `steps` field in the `config` block. The classification method decides where the breaks between classes fall.

## Methods

| Method            | What it does                                                   | Best for                                                           |
| ----------------- | -------------------------------------------------------------- | ------------------------------------------------------------------ |
| `continuous`      | Smooth gradient, no discrete classes                           | Proportional symbols, smooth color ramps                           |
| `jenks`           | Natural breaks — minimizes variance within each class          | Most cases; a good starting point                                  |
| `quantiles`       | Equal number of features in each class                         | Balanced representation, evenly distributed data                   |
| `equal-intervals` | Equal-sized value bins                                         | When the value ranges themselves matter                            |
| `stddev`          | Classes in 1σ steps with the middle class centered on the mean | Data with a near-normal distribution                               |
| `geo-intervals`   | Break points in a geometric progression between min and max    | Multiplicative, strongly skewed data (values must be non-negative) |

All shortcut methods except `continuous` require a `count`. Raster algebra visualizations support only `continuous` and `equal-intervals`.

## Specifying `steps`

There are two ways to define `steps`.

**Shortcut (recommended when you don't know the data's distribution).** Name a method and a class count and let Felt compute the break points from the data:

```json
{
  "config": {
    "numericAttribute": "population_density",
    "steps": {"type": "jenks", "count": 5}
  }
}
```

For a smooth gradient, use the continuous shortcut:

```json
{
  "config": {
    "numericAttribute": "temperature",
    "steps": {"type": "continuous"}
  }
}
```

**Explicit break points (when you know the values you want).** Provide the break points directly:

```json
{
  "config": {
    "numericAttribute": "magnitude",
    "steps": [0, 3, 5, 7, 9]
  }
}
```

## How breaks map to colors and sizes

* **Classed:** `N` break points produce `N − 1` classes. An explicit `color` or `size` array must therefore have exactly `N − 1` entries. (A palette shortcut such as `@galaxy` is expanded for you.)
* **Continuous:** provide **2 or more** colors and Felt interpolates between them across the full value range. For proportional symbols, give `size: [min, max]` and sizes scale smoothly between those two values.

{% hint style="info" %}
For choropleths and other classed thematic maps, 5–7 classes usually reads best. For right-skewed data (common with population or income), prefer `quantiles` or `jenks` over `equal-intervals`. When you have the data's min/max on hand, use them to inform the method and breaks.
{% endhint %}

See [Numeric visualizations](/felt-style-language/types-of-visualizations/numeric-visualizations-color-and-size) for full color and size examples, and [Colors & palettes](/felt-style-language/colors-and-palettes) for palette choices.


# Zoom-based styling

{% hint style="info" %}
Zoom-based styling is useful to change how features and labels are shown at different zoom levels.
{% endhint %}

Most of the properties used on the [`paint`](/felt-style-language/style-definition-blocks/the-paint-block) and [`label`](/felt-style-language/style-definition-blocks/the-label-block) blocks can be defined using interpolators to enable zoom-based styling — the property tables on those pages mark which ones accept an Interpolator.

We support multiple types of interpolators: Step functions, linear, exponential and cubic bezier to enable your map looking like you want at each zoom level. See the [*Interpolators*](/felt-style-language/zoom-based-styling/interpolators) page.

An example of a layer changing feature colors depending on the zoom level can be found below

```json
"paint": {
  "color": {"linear": [[14, "red"], [20, "blue"]]}
}
```

On zoom levels lower than `14`, features of this layer will be rendered in `red` color. On zoom levels higher than `20`, features of this layer will be rendered in `blue` color.

In zooms between `14` and `20`, color will be linearly interpolated between `red` and `blue.`

<figure><img src="/files/KYE31oNHBLvdEHEyVK0P" alt=""><figcaption><p>Features at zoom level 14</p></figcaption></figure>

<figure><img src="/files/nhOrULlZic0a1gJvnzeQ" alt=""><figcaption><p>Features at zoom level 17</p></figcaption></figure>

<figure><img src="/files/EQBMEDSvsUFiPDgWOYO9" alt=""><figcaption><p>Features at zoom level 20</p></figcaption></figure>


# Interpolators

### Interpolators

Interpolators are functions that use the current zoom level to get you a value. The following interpolators are currently supported:

#### Step

`{ "step": [output0, Stops[]] }`: Computes discrete results by evaluating a piecewise-constant function defined by stops on a given input. Returns the output value of the stop with a stop input value just less than the input one. If the input value is less than the input of the first stop, `output0` is returned.

Stops are defined as pairs of `[zoom, value]` where `zoom` is the minimum zoom level where `value` is returned and `value` can be `number | string | boolean`. Note that stops need to be defined by increasing zoom level.

```jsonc
{ "step": ["hsl(50,5%,72%)", [[9, "hsl(10,75%,75%)"]]] }
// If zoom level is less than 9, "hsl(50,5%,72%)" will be returned
// If zoom level is equal or higher than 9, "hsl(10,75%,75%)" will be returned

```

The following image shows the behavior of this definition:

```jsonc
{ "step": [0, [[0, 0], [100, 100]]]} // Blue
{ "step": [0, [[0, 0], [50, 50], [100, 100]]]} // Red
{ "step": [0, [[0, 0], [25, 25], [50, 50], [75, 75], [100, 100]]]} // Yellow

```

<figure><img src="/files/pPceSP7yHGp3sB63Omr0" alt="Graph showing a Step interpolator function"><figcaption></figcaption></figure>

#### Linear

`{ "linear": Stops[] }`: Linearly interpolates between stop values less than or equal and greater than the input value

```jsonc
{
  "linear": [
    [8, 10],
    [14, 15],
    [20, 21]
  ]
}
// If zoom level is less than 8, 10 is returned
// If zoom level is greater or equal than 8 but less than 14, a value linearly interpolated
// between 10 and 15 is returned
// If zoom level is greater or equal than 14 but less than 20, a value linearly interpolated // between 15 and 21 is returned
// If zoom level is greater or equal than 20, 21 is returned

```

The following image shows the behaviour of this definitions

```jsonc
{ "linear": [[0, 0], [100, 100]]} // Blue
{ "linear": [[0, 0], [50, 50], [100, 100]]} // Red
{ "linear": [[0, 0], [25, 25], [50, 50], [75, 75], [100, 100]]} // Yellow

```

<figure><img src="/files/9KyzRBB8jInUnuQf0Uzb" alt="Graph showing a Linear interpolator function"><figcaption></figcaption></figure>

`{ "linear": [number, number] }`: Expands to `{ "linear": [[minZoom, number], [maxZoom, number]] }`

```jsonc
{ "linear": [8, 10] }
// If minZoom is defined as 3 and maxZoom is defined as 20:
// If zoom level is less than 3, 8 is returned
// If zoom level is between 3 and 20, a value linearly interpolated between 8 and 10 is
// returned
// If zoom level is greater or equal than 20, 10 is returned

```

Color linear interpolation is done in the HCL color-space

#### Exponential

`{ "exp": [number, Stops[]] }`: Exponentially interpolates between output stop values less than or equal and greater than the input value. The base parameter controls the rate at which output increases where higher values increase the output value towards the end of the range, lower values increase the output value towards the start of the range, and a base 1 interpolates linearly.

The used value is computed as follows : `(Math.pow(base, progress) - 1) / (Math.pow(base, difference) - 1)`

```jsonc
{
  "exp": [
    0.25,
    [
      [0, 25],
      [10, 100]
    ]
  ]
}
// If zoom level is less than 0, 25 is returned
// If zoom level z is between 0 and 10, an interpolation factor is computed between 0 and 10
// and then it's used to interpolate between 25 and 100
// If zoom level is equal or higher than 10, 100 will be returned

```

The following images shows the behaviour of this definition

```jsonc
{ "exp": [0.25, [[0, 0], [100, 100]]]} // Blue
{ "exp": [0.25, [[0, 0], [50, 50], [100, 100]]]} // Red
{ "exp": [0.25, [[0, 0], [25, 25], [50, 50], [75, 75], [100, 100]]]} // Yellow
```

![](/files/GqTmSrwwyf5d5DFHDU8c)

```jsonc
{ "exp": [0.5, [[0, 0], [100, 100]]]} // Blue
{ "exp": [0.5, [[0, 0], [50, 50], [100, 100]]]} // Red
{ "exp": [0.5, [[0, 0], [25, 25], [50, 50], [75, 75], [100, 100]]]} // Yellow
```

![](/files/Hnhkiy97P6IXxyXVT6wg)

```jsonc
{ "exp": [0.75, [[0, 0], [100, 100]]]} // Blue
{ "exp": [0.75, [[0, 0], [50, 50], [100, 100]]]} // Red
{ "exp": [0.75, [[0, 0], [25, 25], [50, 50], [75, 75], [100, 100]]]} // Yellow
```

![](/files/GDNHk6BBeYtpHwCaXqQi)

```jsonc
{ "exp": [1, [[0, 0], [100, 100]]]} // Blue
{ "exp": [1, [[0, 0], [50, 50], [100, 100]]]} // Red
{ "exp": [1, [[0, 0], [25, 25], [50, 50], [75, 75], [100, 100]]]} // Yellow
```

![](/files/EXSrRTG1Gpfc2GBoJ5iL)

```jsonc
{ "exp": [1.25, [[0, 0], [100, 100]]]} // Blue
{ "exp": [1.25, [[0, 0], [50, 50], [100, 100]]]} // Red
{ "exp": [1.25, [[0, 0], [25, 25], [50, 50], [75, 75], [100, 100]]]} // Yellow
```

![](/files/vscQnVLn9rfFIAbA638N)

```jsonc
{ "exp": [2, [[0, 0], [100, 100]]]} // Blue
{ "exp": [2, [[0, 0], [50, 50], [100, 100]]]} // Red
{ "exp": [2, [[0, 0], [25, 25], [50, 50], [75, 75], [100, 100]]]} // Yellow
```

![](/files/Abr5NovEKu4LTlU6Qk6X)

#### Cubic Bezier

`{ "cubicbezier": [number, number, number, number, Stops[]] }`: Interpolates using the bezier curve defined by the curve control points.

The following images shows the behaviour of this definition

```jsonc
{ "cubicbezier": [0.25, 0, 0.75, 1.5, [[0, 0], [100, 100]]]} // Blue
{ "cubicbezier": [0.25, 0, 0.75, 1.5, [[0, 0], [50, 50], [100, 100]]]} // Red
{ "cubicbezier": [0.25, 0, 0.75, 1.5, [[0, 0], [25, 25], [50, 50], [75, 75], [100, 100]]]} // Yellow
```

![](/files/z63OrwvMBfSMIuMNS9G7)


# Legends

Adding a legend block to a visualization makes a legend entry appear for this visualization.

Each legend entry is shown with the geometry type and color defined by the dataset and the visualization block.

While simple visualizations will generate a single legend entry, categorical visualizations will generate a legend entry per category.

## The `legend` block

The only field is `displayName`. For **categorical** visualizations it maps each category to its label; for **numeric**/**heatmap** visualizations it maps the index of each class (`"0"`, `"1"`, …) to its label. Set `"displayName": "auto"` to let Felt generate the labels for you — from the computed breaks for numeric classes, or from the category names for categorical visualizations (`"auto"` requires `steps` or `categories` to be set in `config`). An empty `legend: {}` still produces a legend entry using the dataset's geometry and color.

For continuous (non-stepped) color-by and heatmap legends keyed by index, `"0"` labels the lowest value and the highest index labels the highest value. Continuous **size** legends are the historical exception: their indices run inverted, with `"0"` labeling the maximum, `"1"` the midpoint, and `"2"` the minimum.

## Simple legend

The Biodiversity Hotspots layer in Felt has a simple visualization with a legend defined as follows:

```json
"legend": {}
```

![](/files/aoYHJNg6ujxDyCfF0pTK)

## Categorical legend

The Plant Hardiness Zones layer in Felt has a categorical visualization with a legend defined as follows:

```json
"legend": {
  "displayName": {
    "13": "13: 60 to 70 °F",
    "12": "12: 50 to 60 °F",
    "11": "11: 40 to 50 °F",
    "10": "10: 30 to 40 °F",
    "9": "9: 20 to 30 °F",
    "8": "8: 10 to 20 °F",
    "7": "7: 0 to 10 °F",
    "6": "6: -10 to 0 °F",
    "5": "5: -20 to -10 °F",
    "4": "4: -30 to -20 °F",
    "3": "3: -40 to -30 °F",
    "2": "2: -50 to -40 °F",
    "1": "1: -60 to -50 °F"
  }
}
```

![](/files/pyGjTKXtT36O8XRuZpbI)

## Numeric legends

The visual display of numeric legends varies based on the style method (stepped or continuous) and the geometry type (point, line, polygon).

The `displayName` can be modified in the `legend` block similar to simple and categorical style types.

### Stepped

```json
"legend": {
  "displayName": {
    "0": "5.14 to 19.46",
    "1": "19.46 to 26.43",
    "2": "26.43 to 34.06",
    "3": "34.06 to 45.06",
    "4": "45.06 to 100"
  }
}
```

![](/files/3Splg2sWDtmD3FKHrExL)

### **Continuous**

```json
"legend": {
  "displayName": {
    "0": "2.34M", 
    "1": "714.65K", 
    "2": "33K"
  }
}
```

![](/files/2rdUZlF0iyVKg9CvwdQP)

## Heatmap legends

Heatmap legends are defined as follows:

```json
"legend": {
  "displayName": {
    "0": "Low", 
    "1": "High"
 }
}
```

![](/files/X3zL3cTbBtbXyHRTDwNC)

Notice that the `displayName` mapping goes from `0` (left value) to `1` (right value)


# Errors

Style validation errors surface as a banner in the in-app style editor, and as `422` responses with the message in the error `detail` when styling [via the REST API](/rest-api/errors-and-rate-limits). The JS SDK rejects the `setLayerStyle` promise with the validation message.

### Unexpected value or type

**Problem**: One of the values set in the style has an unsupported value or an invalid type.

**Solution**: Change the value to be valid.

**Error messages:**

* Attribute `'displayName'` on a legend item of type simple must be a string.
* Attribute `attribute_name` is not a number.
* Attribute `attribute_name` is not a string.
* Attribute `'lineCap'` is not a supported value. Supported values are butt, round, square.
* Attribute `'lineJoin'` is not a supported value. Supported values are bevel, round, miter.
* Visualization dashArray has to be an array with even length.
* Attribute '`offset'` must be either an array of numbers or a number.
* Attribute `'placement'` contains a not supported value. Supported values are `N`, `NE`, `E`, `SE`, `S`, `SW`, `W`, `NW`, `Center`.
* Attribute `'placement'` contains a not supported value. Supported values are `Above`, `Center`, `Below`.
* All values in `'labelAttribute'` must be a string.
* Visualization `'type'` definition must be one of `simple`, `categorical`, `numeric`, `heatmap`, `hillshade`, `h3`.
* Attribute `'showOther'` must be one of `above`, `below`. (Despite the wording, this message refers to the `otherOrder` field — `showOther` itself is a boolean, while `otherOrder` must be `"above"` or `"below"`.)

### Categorical visualization not working

**Problem**: The style defines a categorical visualization, but the maps are not showing the layer

**Error messages:**

* Categories required. A `categories` array must be defined in the *config* block when defining a categorical visualization. Read more about categorical visualizations [here](/felt-style-language/types-of-visualizations/categorical-visualizations).
* Not enough or too many `attribute_name` values. When defining a categorical visualization, all style and label properties must be an array with either a single value that will apply to all categories or an array with as many values as categories defined in the config block. Read more about categorical visualizations [here](/felt-style-language/types-of-visualizations/categorical-visualizations).


# Examples

A compact, one-per-type overview. For fuller, captioned examples — line casing, proportional symbols, icon-per-category, H3 aggregation popups, raster algebra — see the individual [Types of visualizations](/felt-style-language/types-of-visualizations) pages.

### Minimal

```json
{"version": "2.3.1", "type": "simple", "config": {}, "paint": {}, "label": {}}
```

### Point (simple)

```json
{
  "version": "2.3.1",
  "type": "simple",
  "config": {"labelAttribute": ["name"]},
  "paint": {"color": "#8F7EBF", "size": 4, "strokeColor": "auto", "strokeWidth": 1},
  "label": {"color": "auto", "haloColor": "auto", "placement": ["E"], "offset": [6, 0]}
}
```

### Line (simple)

```json
{
  "version": "2.3.1",
  "type": "simple",
  "config": {"labelAttribute": ["river_name"]},
  "paint": {"color": "hsl(217, 80%, 40%)", "size": 2},
  "label": {"color": "auto", "fontStyle": "italic", "placement": "Above", "repeatDistance": 200}
}
```

### Polygon (simple)

```json
{
  "version": "2.3.1",
  "type": "simple",
  "config": {"labelAttribute": ["name"]},
  "paint": {"color": "#3B82F6", "strokeColor": "auto", "strokeWidth": 1, "opacity": 0.8},
  "label": {"color": "auto", "haloColor": "auto", "placement": ["Center"]}
}
```

### Categorical

```json
{
  "version": "2.3.1",
  "type": "categorical",
  "config": {
    "categoricalAttribute": "primary_fuel",
    "categories": {"type": "top", "count": 6},
    "showOther": true
  },
  "paint": {"color": "@catPalette1", "size": 4, "strokeColor": "auto", "strokeWidth": 1},
  "legend": {"displayName": "auto"}
}
```

### Numeric

```json
{
  "version": "2.3.1",
  "type": "numeric",
  "config": {"numericAttribute": "Renter occupied (%)", "steps": {"type": "jenks", "count": 5}},
  "paint": {"color": "@galaxy", "opacity": 0.9, "strokeColor": "auto", "strokeWidth": 0.5},
  "legend": {"displayName": "auto"}
}
```

### Heatmap

```json
{
  "version": "2.3.1",
  "type": "heatmap",
  "config": {},
  "paint": {"color": "@purpYlPink", "size": 10, "intensity": 0.2, "opacity": 0.9},
  "legend": {"displayName": {"0": "Low", "1": "High"}}
}
```

### H3

```json
{
  "version": "2.3.1",
  "type": "h3",
  "config": {
    "aggregation": "sum",
    "numericAttribute": "capacity_mw",
    "baseBinLevel": 3,
    "binMode": "fixed",
    "steps": {"type": "quantiles", "count": 5}
  },
  "paint": {"color": "@riverine"},
  "legend": {"displayName": "auto"}
}
```

### Raster

```json
{
  "version": "2.3.1",
  "type": "numeric",
  "config": {"band": 1, "steps": {"type": "continuous"}, "noData": [-9999], "rasterResampling": "linear"},
  "paint": {"color": "@mplVirdis", "opacity": 1},
  "legend": {"displayName": "auto"}
}
```


# API Reference

To get started:

```
import { Felt } from "@feltmaps/js-sdk";

const felt = await Felt.embed(
  document.querySelector("#container"),
  "FELT_MAP_ID",
  {
    uiControls: {
      cooperativeGestures: false,
      fullScreenButton: false,
      showLegend: false,
    },
  }
);
const layers = await map.getLayers();
```

**View** [**FeltController**](/js-sdk-api-reference/main/feltcontroller) **for a complete list of available functions.** [**FeltEmbedOptions**](/js-sdk-api-reference/main/feltembedoptions) **enumerates initialization options.**

## Documents

* [CHANGELOG](/js-sdk-api-reference/changelog)

## Modules

* [Basemaps](/js-sdk-api-reference/basemaps)
* [Elements](/js-sdk-api-reference/elements)
* [Interactions](/js-sdk-api-reference/interactions)
* [Layers](/js-sdk-api-reference/layers)
* [Main](/js-sdk-api-reference/main)
* [Misc](/js-sdk-api-reference/misc)
* [Selection](/js-sdk-api-reference/selection)
* [Shared](/js-sdk-api-reference/shared)
* [Tools](/js-sdk-api-reference/tools)
* [UI](/js-sdk-api-reference/ui)
* [Viewport](/js-sdk-api-reference/viewport)


# @feltmaps/js-sdk

## 1.10.2

### Patch Changes

* b57ceb5: Make `RasterLayerSource.encodedTileTemplateUrl` nullable (`string | null`). TileService layers (WMS, WMTS, ArcGIS) serve pre-rendered image tiles and have no encoded tile URL.

## 1.10.1

### Patch Changes

* c3b650b: Update layer visible docs
* 6c0d014: Fix `Felt.connect` intermittently failing in Safari with `DataCloneError` by creating a fresh `MessageChannel` for each handshake attempt instead of re-transferring the same port

## 1.10.0

### Minor Changes

* 03be185: Add basemaps API
* 34b9ed3: Add feature actions
* f7a1b4b: Add pageSize and select to getFeatures
* 3d9527b: Add duplicateLayer method

### Patch Changes

* 6e233c6: Fix feature action types and documentation

## 1.9.0

### Minor Changes

* 7e4034f: Add support for legendDisplay on the layer

## 1.8.0

### Minor Changes

* 336538b: Improve controller method documentation

### Patch Changes

* 7b04db4: Update filter docs

## 1.7.0

### Minor Changes

* f40a75c: Update getPrecomputedAggregates types
* 4c4ebfd: Add UIIframeElement to custom ui panel
* b87393f: Better docs, types and method consistency for Custom Panels API
* aff926a: Return UIPanel on custom panel API methods
* f5ca219: Speed up initial Felt/SDK connection
* 0b06e28: Add support for align and distribute items on grid container
* bd1952f: Merge create and update panel methods and require using createPanelId
* 27cc597: Add UIGridContainerElement to custom ui panel
* f2fcd2a: Add checkbox, radio and toggle controls to custom ui panel
* 15a35a5: Add method getPrecomputedAggregates
* 6ce4a76: Rename UIPanel onClose to onClickClose
* c16c55a: Support disabled on custom ui control option
* 2688f9c: Add UIButtonRowElement to custom ui panel
* 49d8895: Add Custom UI API for action triggers
* 0754363: Add new variants, drop thin variants and add tint prop to UIButtonElement
* 535e539: Include element id on every custom ui callback args
* b848937: Add tool setting to hide/show inspector
* 1e8e6f1: Return UIActionTrigger on custom action trigger API methods
* 2b0f08f: Add Custom UI API for panels
* f73dccf: Remove UIButtonGroupElement from custom ui panel

### Patch Changes

* 58bd6dd: Add isDeterministicId to LayerFeature
* 07b27c6: Export bundled controller types file for consumption as single file
* 41e1612: Update getAggregates examples to be correct
* 475c98e: Update getAggregates + MultiAggregationConfig docs
* ea5c7ff: Run patch on prepare, not install
* 4c83cfe: Don't try to patch packages not in dev mode
* 2008356: Documentation improvements
* 2fd720d: Use singular for custom ui types
* a1c6322: Make custom panel API consistent with other methods

## 1.6.0

### Minor Changes

* 160ca6d: Implement getFeatures method on LayersController
* 6a1e536: Widen allowed boundary types
* 56077be: Add setLayerBoundary, getLayerBoundaries, onLayerBoundaryChange

## 1.5.1

### Patch Changes

* ac68984: Update getLayerSchema example

## 1.5.0

### Minor Changes

* d327b46: Add `afterCreation` option in pin tool settings to control what happens after creating a Place
* 6a66d40: Add updateLayer and expand createLayersFromGeoJson options
* 6504fea: Change documentation for getElementGeoemtry to document Highlighter and Marker functionality, and allow holes in Highlighter geometry
* cf6711e: Make programmatic element CRUD types more accurate
* 4c83c60: Add programmatic element creation, editing and deletion
* 46e8ddc: Add onElementChange and onElementDelete
* 3e87812: Adds APIs to use Felt's drawing tools on read-only maps
* d877a83: Add getFeature for getting a single feature as GeoJSON with full detail geometry
* 7f1d6aa: Add "interaction" to element schema
* cf1dd7c: Improve Text and Note types and docs
* 1f6a386: Add getViewportConstraints and setViewportConstraints methods definition
* b059b70: Add onLayerFiltersChange to allow listening to changes to layer filters, be it ephemeral, style or widget filters that changed.
* 19b41ce: Improve createLayer API
* c20e605: Add setLayerLegendVisibility and setLayerGroupLegendVisibility methods definition
* cbfd3fd: Reject promises when method handlers are invalid
* 0915f48: Add showLayerDataTable and hideLayerDataTable methods
* 9620df9: Change Circle.coordinates for Circle.center
* 63c3042: Add getLayerSchema method
* 69cc0a9: Fix spelling mistake in types
* 87304d8: Fix geometry filter type
* 1f22654: Add screen point to pointer events
* b0e4149: Improves type readability and docs
* 65bf269: Add createLayer and deleteLayer
* 4bd0ae9: Add getMapDetails method definition
* 5f903fb: Update Layer type and createLayerFromGeoJson to separate out Source concept
* 597a8d6: Return coordinates on Circle and Place elements as they are only a single point.
* f2f4289: Add layer stats methods

### Patch Changes

* 993fd44: Allow workers to be SDK clients
* 417b8f4: Fixes incorrect value in documentation and updates links to other methods
* 9620df9: Improve element docs
* f0892c4: Improve documentation
* bb79037: Fix per-geometry styling for created layers

## 1.4.0

### Minor Changes

* 555a25a: Add clearSelection method
* 1f5d950: Add option to pass auth token when embedding

## 1.3.0

### Minor Changes

* 4bbde62: Allow setting a note to show with layer filters

## 1.2.0

### Minor Changes

* 7badd4b: Add onMapIdle event
* 41efd53: Add selectFeature method to select feature by layer and feature ID
* 208c492: Add areaQuery param to getRenderedFeatures

## 1.1.0

### Minor Changes

* 5f607ec: Return style with layers, and allow updating layer styles via setLayerStyle

### Patch Changes

* 3a8bec8: Fix API reference link in README

## 1.0.2

### Major Changes

* Release v1 of Felt JS SDK


# Basemaps

***

The Basemaps module allows you to control the map's basemap layer, such as getting the current basemap, listing available basemaps, changing the basemap, and being notified when the basemap changes.

## Controller

* [BasemapsController](/js-sdk-api-reference/basemaps/basemapscontroller)

## Interfaces

* [FeltBasemap](/js-sdk-api-reference/basemaps/feltbasemap)
* [ColorBasemap](/js-sdk-api-reference/basemaps/colorbasemap)
* [CustomTileBasemap](/js-sdk-api-reference/basemaps/customtilebasemap)

## Type Aliases

* [ColorBasemapInput](/js-sdk-api-reference/basemaps/colorbasemapinput)
* [CustomTileBasemapInput](/js-sdk-api-reference/basemaps/customtilebasemapinput)
* [Basemap](/js-sdk-api-reference/basemaps/basemap)


# Basemap

***

> **Basemap**: [`FeltBasemap`](/js-sdk-api-reference/basemaps/feltbasemap) | [`ColorBasemap`](/js-sdk-api-reference/basemaps/colorbasemap) | [`CustomTileBasemap`](/js-sdk-api-reference/basemaps/customtilebasemap)


# BasemapsController

***

The basemaps controller allows you to manage the map's basemap layer.

You can get the current basemap, list available basemaps, change the basemap, and be notified when the basemap changes.

## Extended by

* [`FeltController`](/js-sdk-api-reference/main/feltcontroller)

## Methods

### getCurrentBasemap()

> **getCurrentBasemap**(): `Promise`<[`Basemap`](/js-sdk-api-reference/basemaps/basemap)>

Gets the currently active basemap.

Use this method to retrieve information about the current basemap, including its type (Felt, color, or custom tile), name, color scheme, and attribution.

#### Returns

`Promise`<[`Basemap`](/js-sdk-api-reference/basemaps/basemap)>

A promise that resolves to the current basemap configuration.

#### Example

```typescript
// Get current basemap
const basemap = await felt.getCurrentBasemap();
console.log({
  name: basemap.name,
  type: basemap.type,
  uiColorScheme: basemap.uiColorScheme,
});
```

***

### getBasemaps()

> **getBasemaps**(): `Promise`<[`Basemap`](/js-sdk-api-reference/basemaps/basemap)\[]>

Gets all basemaps available on the map.

Use this method to retrieve a list of all available basemaps that can be applied to the map.

#### Returns

`Promise`<[`Basemap`](/js-sdk-api-reference/basemaps/basemap)\[]>

A promise that resolves to all basemaps available on the map.

#### Example

```typescript
// Get all available basemaps
const basemaps = await felt.getBasemaps();
const lightBasemaps = basemaps.filter(b => b.uiColorScheme === "light");
```

***

### chooseBasemap()

> **chooseBasemap**(`id`: `string`): `void`

Chooses the basemap to use for the map.

Use this method to change the current basemap. The basemap ID can be obtained from getBasemaps().

#### Parameters

| Parameter | Type     |
| --------- | -------- |
| `id`      | `string` |

#### Returns

`void`

A promise that resolves when the basemap has been set.

#### Example

```typescript
// Switch to a specific basemap
const basemaps = await felt.getBasemaps();
const darkBasemap = basemaps.find(b => b.uiColorScheme === "dark");
if (darkBasemap) {
  await felt.chooseBasemap(darkBasemap.id);
}
```

***

### addCustomBasemap()

> **addCustomBasemap**(`args`: { `basemap`: [`ColorBasemapInput`](/js-sdk-api-reference/basemaps/colorbasemapinput) | [`CustomTileBasemapInput`](/js-sdk-api-reference/basemaps/customtilebasemapinput); `select`: `boolean`; }): `Promise`<[`Basemap`](/js-sdk-api-reference/basemaps/basemap)>

Adds a custom basemap to the map. This can be either a solid color or a basemap from a custom tile URL.

#### Parameters

| Parameter      | Type                                                                                                                                                                                              | Description                                    |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| `args`         | { `basemap`: [`ColorBasemapInput`](/js-sdk-api-reference/basemaps/colorbasemapinput) \| [`CustomTileBasemapInput`](/js-sdk-api-reference/basemaps/customtilebasemapinput); `select`: `boolean`; } | -                                              |
| `args.basemap` | [`ColorBasemapInput`](/js-sdk-api-reference/basemaps/colorbasemapinput) \| [`CustomTileBasemapInput`](/js-sdk-api-reference/basemaps/customtilebasemapinput)                                      | The basemap to add.                            |
| `args.select`? | `boolean`                                                                                                                                                                                         | Whether to select the basemap after adding it. |

#### Returns

`Promise`<[`Basemap`](/js-sdk-api-reference/basemaps/basemap)>

A promise for the added basemap.

#### Example

```typescript
// Add a custom basemap and select it
await felt.addCustomBasemap({
  basemap: {
    type: "xyz_tile",
    tileUrl: "https://example.com/tile.png"
  },
  select: true,
});
```

***

### removeBasemap()

> **removeBasemap**(`id`: `string`): `Promise`<`void`>

Removes a basemap from the list of available basemaps.

#### Parameters

| Parameter | Type     |
| --------- | -------- |
| `id`      | `string` |

#### Returns

`Promise`<`void`>

A promise that resolves when the basemap has been removed.

## Events

### onBasemapChange()

> **onBasemapChange**(`args`: { `handler`: (`basemap`: [`Basemap`](/js-sdk-api-reference/basemaps/basemap)) => `void`; }): `VoidFunction`

Adds a listener for when the basemap changes.

Use this to react to basemap changes, such as updating your UI or adjusting other map elements to match the new basemap's color scheme.

#### Parameters

| Parameter      | Type                                                                                       |
| -------------- | ------------------------------------------------------------------------------------------ |
| `args`         | { `handler`: (`basemap`: [`Basemap`](/js-sdk-api-reference/basemaps/basemap)) => `void`; } |
| `args.handler` | (`basemap`: [`Basemap`](/js-sdk-api-reference/basemaps/basemap)) => `void`                 |

#### Returns

`VoidFunction`

A function to unsubscribe from the listener.

#### Example

```typescript
// Listen for basemap changes
const unsubscribe = felt.onBasemapChange({
  handler: basemap => {
    console.log(`Switched to ${basemap.name}`);
    updateUIColors(basemap.uiColorScheme);
  },
});

// later on...
unsubscribe();
```


# ColorBasemap

***

## Properties

### id

> **id**: `string`

A unique identifier for the basemap.

#### Remarks

Do not rely on the stability of this ID for Felt basemaps, as they are subject to change.

***

### name

> **name**: `string`

The name of the basemap.

***

### uiColorScheme

> **uiColorScheme**: `"light"` | `"dark"`

The color scheme of the UI that goes with the basemap. It is best to set this to "light" if your basemap is broadly light, and "dark" if your basemap is broadly dark.

***

### type

> **type**: `"color"`

***

### color

> **color**: `string`

***

### attribution?

> `optional` **attribution**: `string`

The attribution of the basemap, which is shown in the map's UI.


# ColorBasemapInput

***

> **ColorBasemapInput**: `Omit`<[`ColorBasemap`](/js-sdk-api-reference/basemaps/colorbasemap), `"id"`>


# CustomTileBasemap

***

## Properties

### id

> **id**: `string`

A unique identifier for the basemap.

#### Remarks

Do not rely on the stability of this ID for Felt basemaps, as they are subject to change.

***

### name

> **name**: `string`

The name of the basemap.

***

### uiColorScheme

> **uiColorScheme**: `"light"` | `"dark"`

The color scheme of the UI that goes with the basemap. It is best to set this to "light" if your basemap is broadly light, and "dark" if your basemap is broadly dark.

***

### type

> **type**: `"xyz_tile"`

***

### tileUrl

> **tileUrl**: `string`

***

### attribution?

> `optional` **attribution**: `string`

The attribution of the basemap, which is shown in the map's UI.


# CustomTileBasemapInput

***

> **CustomTileBasemapInput**: `Omit`<[`CustomTileBasemap`](/js-sdk-api-reference/basemaps/customtilebasemap), `"id"`>


# FeltBasemap

***

## Properties

### id

> **id**: `string`

A unique identifier for the basemap.

#### Remarks

Do not rely on the stability of this ID for Felt basemaps, as they are subject to change.

***

### name

> **name**: `string`

The name of the basemap.

***

### uiColorScheme

> **uiColorScheme**: `"light"` | `"dark"`

The color scheme of the UI that goes with the basemap. It is best to set this to "light" if your basemap is broadly light, and "dark" if your basemap is broadly dark.

***

### type

> **type**: `"felt"`

***

### theme

> **theme**: `"color_light"` | `"monochrome_dark"` | `"monochrome_light"` | `"satellite"`

***

### attribution?

> `optional` **attribution**: `string`

The attribution of the basemap, which is shown in the map's UI.


# Elements

***

The Felt SDK lets you read, create and update elements on the map.

Elements that are created via the SDK are only available to the current\
session - they are not persisted to the map and not available to other users\
of the map.

> If you want to let your users create elements (as opposed to using the SDK to\
> create them programmatically), you can use the [ToolsController](/js-sdk-api-reference/tools/toolscontroller) to\
> select and configure the drawing tools in Felt.

## Controller

* [ElementsController](/js-sdk-api-reference/elements/elementscontroller)

## Interfaces

* [PlaceElementCreate](/js-sdk-api-reference/elements/placeelementcreate)
* [PathElementCreate](/js-sdk-api-reference/elements/pathelementcreate)
* [PolygonElementCreate](/js-sdk-api-reference/elements/polygonelementcreate)
* [CircleElementCreate](/js-sdk-api-reference/elements/circleelementcreate)
* [MarkerElementCreate](/js-sdk-api-reference/elements/markerelementcreate)
* [HighlighterElementCreate](/js-sdk-api-reference/elements/highlighterelementcreate)
* [TextElementCreate](/js-sdk-api-reference/elements/textelementcreate)
* [NoteElementCreate](/js-sdk-api-reference/elements/noteelementcreate)
* [ImageElementCreate](/js-sdk-api-reference/elements/imageelementcreate)
* [PlaceElementRead](/js-sdk-api-reference/elements/placeelementread)
* [PathElementRead](/js-sdk-api-reference/elements/pathelementread)
* [PolygonElementRead](/js-sdk-api-reference/elements/polygonelementread)
* [CircleElementRead](/js-sdk-api-reference/elements/circleelementread)
* [MarkerElementRead](/js-sdk-api-reference/elements/markerelementread)
* [HighlighterElementRead](/js-sdk-api-reference/elements/highlighterelementread)
* [TextElementRead](/js-sdk-api-reference/elements/textelementread)
* [NoteElementRead](/js-sdk-api-reference/elements/noteelementread)
* [ImageElementRead](/js-sdk-api-reference/elements/imageelementread)
* [LinkElementRead](/js-sdk-api-reference/elements/linkelementread)
* [PlaceElementUpdate](/js-sdk-api-reference/elements/placeelementupdate)
* [PathElementUpdate](/js-sdk-api-reference/elements/pathelementupdate)
* [PolygonElementUpdate](/js-sdk-api-reference/elements/polygonelementupdate)
* [CircleElementUpdate](/js-sdk-api-reference/elements/circleelementupdate)
* [MarkerElementUpdate](/js-sdk-api-reference/elements/markerelementupdate)
* [HighlighterElementUpdate](/js-sdk-api-reference/elements/highlighterelementupdate)
* [TextElementUpdate](/js-sdk-api-reference/elements/textelementupdate)
* [NoteElementUpdate](/js-sdk-api-reference/elements/noteelementupdate)
* [ImageElementUpdate](/js-sdk-api-reference/elements/imageelementupdate)

## Type Aliases

* [ElementCreate](/js-sdk-api-reference/elements/elementcreate)
* [ElementUpdate](/js-sdk-api-reference/elements/elementupdate)

## Element Groups

* [ElementGroup](/js-sdk-api-reference/elements/elementgroup)
* [GetElementGroupsConstraint](/js-sdk-api-reference/elements/getelementgroupsconstraint)
* [ElementGroupChangeCallbackParams](/js-sdk-api-reference/elements/elementgroupchangecallbackparams)

## Elements

* [GetElementsConstraint](/js-sdk-api-reference/elements/getelementsconstraint)
* [ElementChangeCallbackParams](/js-sdk-api-reference/elements/elementchangecallbackparams)
* [Element](/js-sdk-api-reference/elements/element)


# CircleElementCreate

***

## Properties

### type

> **type**: `"Circle"`

***

### radius

> **radius**: `number`

The radius of the circle in meters.

***

### center

> **center**: [`LngLatTuple`](/js-sdk-api-reference/shared/lnglattuple)

The center of the circle.

***

### groupId?

> `optional` **groupId**: `null` | `string`

The ID of the element group that the element belongs to.\
For elements that are not part of a group, this will be null.

***

### color?

> `optional` **color**: `string`

The color of the element in some CSS-like format.

#### Example

```typescript
"#ABC123";
"rgb(255, 0, 0)";
"hsl(200, 100%, 50%)";
```

#### Default

```ts
"#C93535"
```

***

### name?

> `optional` **name**: `null` | `string`

The element's name. For elements that can show a label or text on\
the map (e.g. a Place or Text element) this is the text that will be shown.

For elements such as Polygons or Paths, the name is what is shown when\
the element is selected by clicking on it.

***

### description?

> `optional` **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it\
is selected.

Note that some elements are not selectable on the map, such as Notes, Text\
and Markers, so their description will not be shown.

***

### attributes?

> `optional` **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not\
part of the element's core data, such as a Place's address or some other\
data.

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by\
the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user,\
which is often used for elements that you don't want the user to edit or move by\
accident.

Elements that were created by the map author (i.e. not during an SDK "session") are\
not editable and have special behaviour depending on their name, description and\
attributes.

#### Default

```ts
"default"
```

***

### imageUrl?

> `optional` **imageUrl**: `null` | `string`

The URL of an image that has been added to the element.

***

### strokeOpacity?

> `optional` **strokeOpacity**: `number`

A value between 0 and 1 that describes the opacity of the element's stroke.

#### Default

```ts
1
```

***

### strokeWidth?

> `optional` **strokeWidth**: `number`

The width of the element's stroke in pixels.

#### Default

```ts
2
```

***

### strokeStyle?

> `optional` **strokeStyle**: `"solid"` | `"dashed"` | `"dotted"`

The style of the element's stroke.

#### Default

```ts
"solid"
```

***

### radiusMarker?

> `optional` **radiusMarker**: `boolean`

Whether to show a marker on the circle that indicates the radius

#### Default

```ts
false
```

***

### radiusDisplayAngle?

> `optional` **radiusDisplayAngle**: `number`

The angle at which the control point for setting the radius is displayed,\
in degrees. When the `radiusMarker` is `true`, there is a dotted line rendered\
from the center of the circle to the control point, and the marker is shown\
at the midpoint of this line.

#### Default

```ts
90
```

***

### radiusDisplayUnit?

> `optional` **radiusDisplayUnit**: `null` | `"meter"` | `"kilometer"` | `"foot"` | `"mile"`

The unit of the radius used when the `radiusMarker` is `true`.

A value of `null` means that the unit matches the user's locale.

#### Default

```ts
null
```

***

### fillOpacity?

> `optional` **fillOpacity**: `number`

The opacity of the circle's fill.

#### Default

```ts
0.25
```


# CircleElementRead

***

## Properties

### id

> **id**: `string`

The unique identifier for the element.

***

### groupId

> **groupId**: `null` | `string`

The ID of the element group that the element belongs to.\
For elements that are not part of a group, this will be null.

***

### color

> **color**: `string`

The color of the element in some CSS-like format.

#### Example

```typescript
"#ABC123";
"rgb(255, 0, 0)";
"hsl(200, 100%, 50%)";
```

#### Default

```ts
"#C93535"
```

***

### name

> **name**: `null` | `string`

The element's name. For elements that can show a label or text on\
the map (e.g. a Place or Text element) this is the text that will be shown.

For elements such as Polygons or Paths, the name is what is shown when\
the element is selected by clicking on it.

***

### description

> **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it\
is selected.

Note that some elements are not selectable on the map, such as Notes, Text\
and Markers, so their description will not be shown.

***

### attributes

> **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not\
part of the element's core data, such as a Place's address or some other\
data.

***

### imageUrl

> **imageUrl**: `null` | `string`

The URL of an image that has been added to the element.

***

### strokeOpacity

> **strokeOpacity**: `number`

A value between 0 and 1 that describes the opacity of the element's stroke.

#### Default

```ts
1
```

***

### strokeWidth

> **strokeWidth**: `number`

The width of the element's stroke in pixels.

#### Default

```ts
2
```

***

### strokeStyle

> **strokeStyle**: `"solid"` | `"dashed"` | `"dotted"`

The style of the element's stroke.

#### Default

```ts
"solid"
```

***

### type

> **type**: `"Circle"`

***

### radius

> **radius**: `number`

The radius of the circle in meters.

***

### radiusMarker

> **radiusMarker**: `boolean`

Whether to show a marker on the circle that indicates the radius

#### Default

```ts
false
```

***

### radiusDisplayAngle

> **radiusDisplayAngle**: `number`

The angle at which the control point for setting the radius is displayed,\
in degrees. When the `radiusMarker` is `true`, there is a dotted line rendered\
from the center of the circle to the control point, and the marker is shown\
at the midpoint of this line.

#### Default

```ts
90
```

***

### radiusDisplayUnit

> **radiusDisplayUnit**: `null` | `"meter"` | `"kilometer"` | `"foot"` | `"mile"`

The unit of the radius used when the `radiusMarker` is `true`.

A value of `null` means that the unit matches the user's locale.

#### Default

```ts
null
```

***

### fillOpacity

> **fillOpacity**: `number`

The opacity of the circle's fill.

#### Default

```ts
0.25
```

***

### center

> **center**: [`LngLatTuple`](/js-sdk-api-reference/shared/lnglattuple)

The center of the circle.

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by\
the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user,\
which is often used for elements that you don't want the user to edit or move by\
accident.

Elements that were created by the map author (i.e. not during an SDK "session") are\
not editable and have special behaviour depending on their name, description and\
attributes.

#### Default

```ts
"default"
```


# CircleElementUpdate

***

## Properties

### id

> **id**: `string`

The unique identifier for the element.

***

### type

> **type**: `"Circle"`

***

### groupId?

> `optional` **groupId**: `null` | `string`

The ID of the element group that the element belongs to.\
For elements that are not part of a group, this will be null.

***

### color?

> `optional` **color**: `string`

The color of the element in some CSS-like format.

#### Example

```typescript
"#ABC123";
"rgb(255, 0, 0)";
"hsl(200, 100%, 50%)";
```

#### Default

```ts
"#C93535"
```

***

### name?

> `optional` **name**: `null` | `string`

The element's name. For elements that can show a label or text on\
the map (e.g. a Place or Text element) this is the text that will be shown.

For elements such as Polygons or Paths, the name is what is shown when\
the element is selected by clicking on it.

***

### description?

> `optional` **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it\
is selected.

Note that some elements are not selectable on the map, such as Notes, Text\
and Markers, so their description will not be shown.

***

### attributes?

> `optional` **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not\
part of the element's core data, such as a Place's address or some other\
data.

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by\
the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user,\
which is often used for elements that you don't want the user to edit or move by\
accident.

Elements that were created by the map author (i.e. not during an SDK "session") are\
not editable and have special behaviour depending on their name, description and\
attributes.

#### Default

```ts
"default"
```

***

### imageUrl?

> `optional` **imageUrl**: `null` | `string`

The URL of an image that has been added to the element.

***

### strokeOpacity?

> `optional` **strokeOpacity**: `number`

A value between 0 and 1 that describes the opacity of the element's stroke.

#### Default

```ts
1
```

***

### strokeWidth?

> `optional` **strokeWidth**: `number`

The width of the element's stroke in pixels.

#### Default

```ts
2
```

***

### strokeStyle?

> `optional` **strokeStyle**: `"solid"` | `"dashed"` | `"dotted"`

The style of the element's stroke.

#### Default

```ts
"solid"
```

***

### radius?

> `optional` **radius**: `number`

The radius of the circle in meters.

***

### radiusMarker?

> `optional` **radiusMarker**: `boolean`

Whether to show a marker on the circle that indicates the radius

#### Default

```ts
false
```

***

### radiusDisplayAngle?

> `optional` **radiusDisplayAngle**: `number`

The angle at which the control point for setting the radius is displayed,\
in degrees. When the `radiusMarker` is `true`, there is a dotted line rendered\
from the center of the circle to the control point, and the marker is shown\
at the midpoint of this line.

#### Default

```ts
90
```

***

### radiusDisplayUnit?

> `optional` **radiusDisplayUnit**: `null` | `"meter"` | `"kilometer"` | `"foot"` | `"mile"`

The unit of the radius used when the `radiusMarker` is `true`.

A value of `null` means that the unit matches the user's locale.

#### Default

```ts
null
```

***

### fillOpacity?

> `optional` **fillOpacity**: `number`

The opacity of the circle's fill.

#### Default

```ts
0.25
```

***

### center?

> `optional` **center**: [`LngLatTuple`](/js-sdk-api-reference/shared/lnglattuple)

The center of the circle.


# Element

***

> **Element**: [`PlaceElementRead`](/js-sdk-api-reference/elements/placeelementread) | [`PathElementRead`](/js-sdk-api-reference/elements/pathelementread) | [`PolygonElementRead`](/js-sdk-api-reference/elements/polygonelementread) | [`CircleElementRead`](/js-sdk-api-reference/elements/circleelementread) | [`MarkerElementRead`](/js-sdk-api-reference/elements/markerelementread) | [`HighlighterElementRead`](/js-sdk-api-reference/elements/highlighterelementread) | [`TextElementRead`](/js-sdk-api-reference/elements/textelementread) | [`NoteElementRead`](/js-sdk-api-reference/elements/noteelementread) | [`ImageElementRead`](/js-sdk-api-reference/elements/imageelementread) | [`LinkElementRead`](/js-sdk-api-reference/elements/linkelementread)


# ElementChangeCallbackParams

***

The parameters for the [\`onElementChange\`](/js-sdk-api-reference/elements/elementscontroller#onelementchange) and the [\`onElementCreate\`](/js-sdk-api-reference/elements/elementscontroller#onelementcreate) listeners.

## Properties

### element

> **element**: `null` | [`Element`](/js-sdk-api-reference/elements/element)

The new data for the element or null if the element was removed.

***

### isBeingCreated

> **isBeingCreated**: `boolean`

Whether or not this element is still being created by a drawing tool.

For example, if the user begins drawing a polygon, they need to place multiple points until they've ultimately completed the polygon. All the time they are still placing points, this will be true.

For elements that require text entry (such as Places, Text and Notes) this will be true all the time the user is typing text until the point at which the user finishes, by pressing Escape for example.

If the user is editing an existing element, this will be false.

For elements that are created programmatically, this will be false.


# ElementCreate

***

> **ElementCreate**: [`PlaceElementCreate`](/js-sdk-api-reference/elements/placeelementcreate) | [`PathElementCreate`](/js-sdk-api-reference/elements/pathelementcreate) | [`PolygonElementCreate`](/js-sdk-api-reference/elements/polygonelementcreate) | [`CircleElementCreate`](/js-sdk-api-reference/elements/circleelementcreate) | [`MarkerElementCreate`](/js-sdk-api-reference/elements/markerelementcreate) | [`HighlighterElementCreate`](/js-sdk-api-reference/elements/highlighterelementcreate) | [`ImageElementCreate`](/js-sdk-api-reference/elements/imageelementcreate) | [`TextElementCreate`](/js-sdk-api-reference/elements/textelementcreate) | [`NoteElementCreate`](/js-sdk-api-reference/elements/noteelementcreate)


# ElementGroup

***

## Properties

### id

> **id**: `string`

A string identifying the element group.

***

### name

> **name**: `string`

The name of the element group. This is shown in the legend.

***

### caption

> **caption**: `null` | `string`

The caption of the element group. This is shown in the legend.

***

### elementIds

> **elementIds**: `string`\[]

The ids of the elements in the element group.

#### Remarks

You can use these ids to get the full element objects via the [\`getElements\`](/js-sdk-api-reference/elements/elementscontroller#getelements) method.

***

### visible

> **visible**: `boolean`

Whether the element group is visible or not.

***

### shownInLegend

> **shownInLegend**: `boolean`

Whether the element group is shown in the legend or not.


# ElementGroupChangeCallbackParams

***

The parameters for the [\`onElementGroupChange\`](/js-sdk-api-reference/elements/elementscontroller#onelementgroupchange) listener.

## Properties

### elementGroup

> **elementGroup**: `null` | [`ElementGroup`](/js-sdk-api-reference/elements/elementgroup)


# ElementUpdate

***

> **ElementUpdate**: [`PlaceElementUpdate`](/js-sdk-api-reference/elements/placeelementupdate) | [`PathElementUpdate`](/js-sdk-api-reference/elements/pathelementupdate) | [`PolygonElementUpdate`](/js-sdk-api-reference/elements/polygonelementupdate) | [`CircleElementUpdate`](/js-sdk-api-reference/elements/circleelementupdate) | [`MarkerElementUpdate`](/js-sdk-api-reference/elements/markerelementupdate) | [`HighlighterElementUpdate`](/js-sdk-api-reference/elements/highlighterelementupdate) | [`TextElementUpdate`](/js-sdk-api-reference/elements/textelementupdate) | [`NoteElementUpdate`](/js-sdk-api-reference/elements/noteelementupdate) | [`ImageElementUpdate`](/js-sdk-api-reference/elements/imageelementupdate)


# ElementsController

***

The Elements controller allows you to get information about the elements on the map, and make changes to their visibility.

## Extended by

* [`FeltController`](/js-sdk-api-reference/main/feltcontroller)

## Methods

### getElement()

> **getElement**(`id`: `string`): `Promise`<`null` | [`Element`](/js-sdk-api-reference/elements/element)>

Get a single element from the map by its id.

Use this method when you know the specific ID of an element and want to retrieve its current state. This is more efficient than getting all elements and filtering.

#### Parameters

| Parameter | Type     | Description                            |
| --------- | -------- | -------------------------------------- |
| `id`      | `string` | The id of the element you want to get. |

#### Returns

`Promise`<`null` | [`Element`](/js-sdk-api-reference/elements/element)>

A promise that resolves to the requested element, or `null` if not found.

#### Example

```typescript
const element = await felt.getElement("element-1");
```

***

### getElementGeometry()

> **getElementGeometry**(`id`: `string`): `Promise`<`null` | [`GeoJsonGeometry`](/js-sdk-api-reference/shared/geojsongeometry)>

Get the geometry of an element in GeoJSON geometry format.

For most element types, the geometry returned is based on the `coordinates` property of the element, with some differences:

* For Circle elements, the geometry is a Polygon drawn from the `center` and `radius` properties.
* Path elements become MultiLineString geometries.
* Marker elements return a MultiLineString of the path traced by the user as they drew the marker. Note that this is not the polygon formed by filled-in "pen" stroke, which doesn't exactly follow the path traced by the user as it is smoothed and interpolated to create a continuous line.
* Text, Note and Image elements do not return geometry, so will return `null`.

Use this method when you need the geometric representation of an element for spatial analysis or visualization purposes.

#### Parameters

| Parameter | Type     | Description                                            |
| --------- | -------- | ------------------------------------------------------ |
| `id`      | `string` | The id of the element you want to get the geometry of. |

#### Returns

`Promise`<`null` | [`GeoJsonGeometry`](/js-sdk-api-reference/shared/geojsongeometry)>

A promise that resolves to the element's geometry in GeoJSON format, or `null` if the element has no geometry.

#### Example

```typescript
const geometry = await felt.getElementGeometry("element-1");
console.log(geometry?.type, geometry?.coordinates);
```

***

### getElements()

> **getElements**(`constraint`?: [`GetElementsConstraint`](/js-sdk-api-reference/elements/getelementsconstraint)): `Promise`<(`null` | [`Element`](/js-sdk-api-reference/elements/element))\[]>

Gets elements from the map, according to the constraints supplied. If no constraints are supplied, all elements will be returned.

Use this method to retrieve multiple elements, optionally filtered by constraints. This is useful for bulk operations or when you need to analyze all elements on the map.

#### Parameters

| Parameter     | Type                                                                            | Description                                                          |
| ------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `constraint`? | [`GetElementsConstraint`](/js-sdk-api-reference/elements/getelementsconstraint) | Optional constraints to apply to the elements returned from the map. |

#### Returns

`Promise`<(`null` | [`Element`](/js-sdk-api-reference/elements/element))\[]>

A promise that resolves to an array of elements, ordered by the order specified in Felt.

#### Remarks

The elements in the map, ordered by the order specified in Felt. This is not necessarily the order that they are drawn in, as Felt draws points above lines and lines above polygons, for instance.

#### Example

```typescript
const elements = await felt.getElements();
```

***

### getElementGroup()

> **getElementGroup**(`id`: `string`): `Promise`<`null` | [`ElementGroup`](/js-sdk-api-reference/elements/elementgroup)>

Get an element group from the map by its id.

Element groups allow you to organize related elements together and control their visibility as a unit.

#### Parameters

| Parameter | Type     | Description                                  |
| --------- | -------- | -------------------------------------------- |
| `id`      | `string` | The id of the element group you want to get. |

#### Returns

`Promise`<`null` | [`ElementGroup`](/js-sdk-api-reference/elements/elementgroup)>

A promise that resolves to the requested element group, or `null` if not found.

#### Example

```typescript
const elementGroup = await felt.getElementGroup("element-group-1");
```

***

### getElementGroups()

> **getElementGroups**(`constraint`?: [`GetElementGroupsConstraint`](/js-sdk-api-reference/elements/getelementgroupsconstraint)): `Promise`<(`null` | [`ElementGroup`](/js-sdk-api-reference/elements/elementgroup))\[]>

Gets element groups from the map, according to the filters supplied. If no constraints are supplied, all element groups will be returned in rendering order.

Use this method to retrieve multiple element groups, optionally filtered by constraints. This is useful for bulk operations on element groups.

#### Parameters

| Parameter     | Type                                                                                      | Description                                                                |
| ------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `constraint`? | [`GetElementGroupsConstraint`](/js-sdk-api-reference/elements/getelementgroupsconstraint) | Optional constraints to apply to the element groups returned from the map. |

#### Returns

`Promise`<(`null` | [`ElementGroup`](/js-sdk-api-reference/elements/elementgroup))\[]>

A promise that resolves to an array of element groups in rendering order.

#### Example

```typescript
const elementGroups = await felt.getElementGroups({ ids: ["element-group-1", "element-group-2"] });
```

***

### setElementGroupVisibility()

> **setElementGroupVisibility**(`visibility`: [`SetVisibilityRequest`](/js-sdk-api-reference/shared/setvisibilityrequest)): `Promise`<`void`>

Hide or show element groups with the given ids.

Use this method to control the visibility of multiple element groups at once. This is more efficient than hiding/showing individual elements.

#### Parameters

| Parameter    | Type                                                                        | Description                                      |
| ------------ | --------------------------------------------------------------------------- | ------------------------------------------------ |
| `visibility` | [`SetVisibilityRequest`](/js-sdk-api-reference/shared/setvisibilityrequest) | The visibility configuration for element groups. |

#### Returns

`Promise`<`void`>

A promise that resolves when the visibility changes are applied.

#### Example

```typescript
felt.setElementGroupVisibility({ show: ["element-group-1", "element-group-2"], hide: ["element-group-3"] });
```

***

### createElement()

> **createElement**(`element`: [`ElementCreate`](/js-sdk-api-reference/elements/elementcreate)): `Promise`<[`Element`](/js-sdk-api-reference/elements/element)>

Create a new element on the map.

Use this method to programmatically create elements on the map. Elements created via the SDK are only available to the current session and are not persisted.

#### Parameters

| Parameter | Type                                                            | Description                          |
| --------- | --------------------------------------------------------------- | ------------------------------------ |
| `element` | [`ElementCreate`](/js-sdk-api-reference/elements/elementcreate) | The element configuration to create. |

#### Returns

`Promise`<[`Element`](/js-sdk-api-reference/elements/element)>

A promise that resolves to the created element.

#### Example

```typescript
const element = await felt.createElement({ type: "Place", coordinates: [10, 10] });
```

***

### updateElement()

> **updateElement**(`element`: [`ElementUpdate`](/js-sdk-api-reference/elements/elementupdate)): `Promise`<[`Element`](/js-sdk-api-reference/elements/element)>

Update an element on the map. The element type must be specified.

Use this method to modify existing elements. You can update properties like coordinates, styling, and metadata.

#### Parameters

| Parameter | Type                                                            | Description                       |
| --------- | --------------------------------------------------------------- | --------------------------------- |
| `element` | [`ElementUpdate`](/js-sdk-api-reference/elements/elementupdate) | The element update configuration. |

#### Returns

`Promise`<[`Element`](/js-sdk-api-reference/elements/element)>

A promise that resolves to the updated element.

#### Example

```typescript
// Update a place element's coordinates
await felt.updateElement({
  id: "element-1",
  type: "Place",
  coordinates: [10, 20]
});

// Update a polygon's style
await felt.updateElement({
  id: "element-2",
  type: "Polygon",
  color: "#ABC123",
  fillOpacity: 0.5
});
```

***

### deleteElement()

> **deleteElement**(`id`: `string`): `Promise`<`void`>

Delete an element from the map.

Use this method to remove elements from the map. This operation cannot be undone.

#### Parameters

| Parameter | Type     | Description                      |
| --------- | -------- | -------------------------------- |
| `id`      | `string` | The id of the element to delete. |

#### Returns

`Promise`<`void`>

A promise that resolves when the element is deleted.

#### Example

```typescript
await felt.deleteElement("element-1");
```

## Events

### onElementCreate()

> **onElementCreate**(`args`: { `handler`: (`change`: [`ElementChangeCallbackParams`](/js-sdk-api-reference/elements/elementchangecallbackparams)) => `void`; }): `VoidFunction`

Adds a listener for when an element is created.

This will fire when elements are created programmatically, or when the user starts creating an element with a drawing tool.

When the user creates an element with a drawing tool, it can begin in an invalid state, such as if you've just placed a single point in a polygon.

You can use the `isBeingCreated` property to determine if the element is still being created by a drawing tool.

If you want to know when the element is finished being created, you can use the [\`onElementCreateEnd\`](#onelementcreateend) listener.

#### Parameters

| Parameter      | Type                                                                                                                              | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `args`         | { `handler`: (`change`: [`ElementChangeCallbackParams`](/js-sdk-api-reference/elements/elementchangecallbackparams)) => `void`; } | -                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `args.handler` | (`change`: [`ElementChangeCallbackParams`](/js-sdk-api-reference/elements/elementchangecallbackparams)) => `void`                 | The handler that is called when an element is created. This will fire when elements are created programmatically, or when the user starts creating an element with a drawing tool. When the user creates an element with a drawing tool, it can begin in an invalid state, such as if you've just placed a single point in a polygon. You can use the `isBeingCreated` property to determine if the element is still being created by a drawing tool. If you want to know when the element is finished being created, you can use the [\`onElementCreateEnd\`](#onelementcreateend) listener. |

#### Returns

`VoidFunction`

A function to unsubscribe from the listener.

#### Example

```typescript
const unsubscribe = felt.onElementCreate({
  handler: ({isBeingCreated, element}) => console.log(element.id),
});

// later on...
unsubscribe();
```

***

### onElementCreateEnd()

> **onElementCreateEnd**(`args`: { `handler`: (`params`: { `element`: [`Element`](/js-sdk-api-reference/elements/element); }) => `void`; }): `VoidFunction`

Listens for when a new element is finished being created by a drawing tool.

This differs from the [\`onElementCreate\`](#onelementcreate) listener, which fires whenever an element is first created. This fires when the user finishes creating an element which could be after a series of interactions.

For example, when creating a polygon, the user places a series of points then finishes by pressing Enter or Escape. Or when creating a Place element, they add the marker, type a label, then finally deselect the element.

#### Parameters

| Parameter      | Type                                                                                                      | Description                                    |
| -------------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| `args`         | { `handler`: (`params`: { `element`: [`Element`](/js-sdk-api-reference/elements/element); }) => `void`; } | -                                              |
| `args.handler` | (`params`: { `element`: [`Element`](/js-sdk-api-reference/elements/element); }) => `void`                 | The handler to call whenever this event fires. |

#### Returns

`VoidFunction`

A function to unsubscribe from the listener.

#### Example

```typescript
const unsubscribe = felt.onElementCreateEnd({
  handler: (params) => console.log(params),
});

// later on...
unsubscribe();
```

***

### onElementChange()

> **onElementChange**(`args`: { `options`: { `id`: `string`; }; `handler`: (`change`: [`ElementChangeCallbackParams`](/js-sdk-api-reference/elements/elementchangecallbackparams)) => `void`; }): `VoidFunction`

Adds a listener for when an element changes.

This will fire when an element is being edited, either on the map by the user or programmatically.

Like the [\`onElementCreate\`](#onelementcreate) listener, this will fire when an element is still being created by a drawing tool.

You can check the [\`isBeingCreated\`](/js-sdk-api-reference/elements/elementchangecallbackparams#isbeingcreated) property to determine if the element is still being created by a drawing tool.

#### Parameters

| Parameter         | Type                                                                                                                                                              | Description                                          |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `args`            | { `options`: { `id`: `string`; }; `handler`: (`change`: [`ElementChangeCallbackParams`](/js-sdk-api-reference/elements/elementchangecallbackparams)) => `void`; } | -                                                    |
| `args.options`    | { `id`: `string`; }                                                                                                                                               | -                                                    |
| `args.options.id` | `string`                                                                                                                                                          | The id of the element to listen for changes to.      |
| `args.handler`    | (`change`: [`ElementChangeCallbackParams`](/js-sdk-api-reference/elements/elementchangecallbackparams)) => `void`                                                 | The handler that is called when the element changes. |

#### Returns

`VoidFunction`

A function to unsubscribe from the listener.

#### Example

```typescript
const unsubscribe = felt.onElementChange({
  options: { id: "element-1" },
  handler: ({element}) => console.log(element.id),
});

// later on...
unsubscribe();
```

***

### onElementDelete()

> **onElementDelete**(`args`: { `options`: { `id`: `string`; }; `handler`: () => `void`; }): `VoidFunction`

Adds a listener for when an element is deleted.

Use this to react to element deletions, such as cleaning up related data or updating your application state.

#### Parameters

| Parameter         | Type                                                         | Description                                             |
| ----------------- | ------------------------------------------------------------ | ------------------------------------------------------- |
| `args`            | { `options`: { `id`: `string`; }; `handler`: () => `void`; } | -                                                       |
| `args.options`    | { `id`: `string`; }                                          | -                                                       |
| `args.options.id` | `string`                                                     | The id of the element to listen for deletions of.       |
| `args.handler`    | () => `void`                                                 | The handler that is called when the element is deleted. |

#### Returns

`VoidFunction`

A function to unsubscribe from the listener.

#### Example

```typescript
const unsubscribe = felt.onElementDelete({
  options: { id: "element-1" },
  handler: () => console.log("element-1 deleted"),
});

// later on...
unsubscribe();
```

***

### onElementGroupChange()

> **onElementGroupChange**(`args`: { `options`: { `id`: `string`; }; `handler`: (`change`: [`ElementGroupChangeCallbackParams`](/js-sdk-api-reference/elements/elementgroupchangecallbackparams)) => `void`; }): `VoidFunction`

Adds a listener for when an element group changes.

Use this to react to changes in element groups, such as when elements are added to or removed from groups.

#### Parameters

| Parameter         | Type                                                                                                                                                                        |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `args`            | { `options`: { `id`: `string`; }; `handler`: (`change`: [`ElementGroupChangeCallbackParams`](/js-sdk-api-reference/elements/elementgroupchangecallbackparams)) => `void`; } |
| `args.options`    | { `id`: `string`; }                                                                                                                                                         |
| `args.options.id` | `string`                                                                                                                                                                    |
| `args.handler`    | (`change`: [`ElementGroupChangeCallbackParams`](/js-sdk-api-reference/elements/elementgroupchangecallbackparams)) => `void`                                                 |

#### Returns

`VoidFunction`

A function to unsubscribe from the listener.

#### Example

```typescript
const unsubscribe = felt.onElementGroupChange({
  options: { id: "element-group-1" },
  handler: elementGroup => console.log(elementGroup.id),
});

// later on...
unsubscribe();
```


# GetElementGroupsConstraint

***

The constraints to apply when getting element groups.

## Properties

### ids?

> `optional` **ids**: `string`\[]

The ids of the element groups to get.


# GetElementsConstraint

***

The constraints to apply when getting elements.

## Properties

### ids?

> `optional` **ids**: `string`\[]

The ids of the elements to get.


# HighlighterElementCreate

***

## Properties

### type

> **type**: `"Highlighter"`

***

### coordinates

> **coordinates**: [`LngLatTuple`](/js-sdk-api-reference/shared/lnglattuple)\[]\[]\[]

A multipolygon describing the area that is highlighted.

If `renderHoles` is set to false, only the outer ring of each polygon\
will be rendered, filling in the area inside the highlighted region.

***

### groupId?

> `optional` **groupId**: `null` | `string`

The ID of the element group that the element belongs to.\
For elements that are not part of a group, this will be null.

***

### color?

> `optional` **color**: `string`

The color of the element in some CSS-like format.

#### Example

```typescript
"#ABC123";
"rgb(255, 0, 0)";
"hsl(200, 100%, 50%)";
```

#### Default

```ts
"#C93535"
```

***

### name?

> `optional` **name**: `null` | `string`

The element's name. For elements that can show a label or text on\
the map (e.g. a Place or Text element) this is the text that will be shown.

For elements such as Polygons or Paths, the name is what is shown when\
the element is selected by clicking on it.

***

### description?

> `optional` **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it\
is selected.

Note that some elements are not selectable on the map, such as Notes, Text\
and Markers, so their description will not be shown.

***

### attributes?

> `optional` **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not\
part of the element's core data, such as a Place's address or some other\
data.

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by\
the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user,\
which is often used for elements that you don't want the user to edit or move by\
accident.

Elements that were created by the map author (i.e. not during an SDK "session") are\
not editable and have special behaviour depending on their name, description and\
attributes.

#### Default

```ts
"default"
```

***

### renderHoles?

> `optional` **renderHoles**: `boolean`

Whether to render the holes of the highlighted area.

#### Default

```ts
false
```

***

### opacity?

> `optional` **opacity**: `number`

The opacity of the highlighter, between 0 and 1.

#### Default

```ts
0.5
```


# HighlighterElementRead

***

## Properties

### id

> **id**: `string`

The unique identifier for the element.

***

### groupId

> **groupId**: `null` | `string`

The ID of the element group that the element belongs to.\
For elements that are not part of a group, this will be null.

***

### color

> **color**: `string`

The color of the element in some CSS-like format.

#### Example

```typescript
"#ABC123";
"rgb(255, 0, 0)";
"hsl(200, 100%, 50%)";
```

#### Default

```ts
"#C93535"
```

***

### name

> **name**: `null` | `string`

The element's name. For elements that can show a label or text on\
the map (e.g. a Place or Text element) this is the text that will be shown.

For elements such as Polygons or Paths, the name is what is shown when\
the element is selected by clicking on it.

***

### description

> **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it\
is selected.

Note that some elements are not selectable on the map, such as Notes, Text\
and Markers, so their description will not be shown.

***

### attributes

> **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not\
part of the element's core data, such as a Place's address or some other\
data.

***

### type

> **type**: `"Highlighter"`

***

### renderHoles

> **renderHoles**: `boolean`

Whether to render the holes of the highlighted area.

#### Default

```ts
false
```

***

### opacity

> **opacity**: `number`

The opacity of the highlighter, between 0 and 1.

#### Default

```ts
0.5
```

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by\
the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user,\
which is often used for elements that you don't want the user to edit or move by\
accident.

Elements that were created by the map author (i.e. not during an SDK "session") are\
not editable and have special behaviour depending on their name, description and\
attributes.

#### Default

```ts
"default"
```


# HighlighterElementUpdate

***

## Properties

### id

> **id**: `string`

The unique identifier for the element.

***

### type

> **type**: `"Highlighter"`

***

### groupId?

> `optional` **groupId**: `null` | `string`

The ID of the element group that the element belongs to.\
For elements that are not part of a group, this will be null.

***

### color?

> `optional` **color**: `string`

The color of the element in some CSS-like format.

#### Example

```typescript
"#ABC123";
"rgb(255, 0, 0)";
"hsl(200, 100%, 50%)";
```

#### Default

```ts
"#C93535"
```

***

### name?

> `optional` **name**: `null` | `string`

The element's name. For elements that can show a label or text on\
the map (e.g. a Place or Text element) this is the text that will be shown.

For elements such as Polygons or Paths, the name is what is shown when\
the element is selected by clicking on it.

***

### description?

> `optional` **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it\
is selected.

Note that some elements are not selectable on the map, such as Notes, Text\
and Markers, so their description will not be shown.

***

### attributes?

> `optional` **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not\
part of the element's core data, such as a Place's address or some other\
data.

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by\
the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user,\
which is often used for elements that you don't want the user to edit or move by\
accident.

Elements that were created by the map author (i.e. not during an SDK "session") are\
not editable and have special behaviour depending on their name, description and\
attributes.

#### Default

```ts
"default"
```

***

### renderHoles?

> `optional` **renderHoles**: `boolean`

Whether to render the holes of the highlighted area.

#### Default

```ts
false
```

***

### opacity?

> `optional` **opacity**: `number`

The opacity of the highlighter, between 0 and 1.

#### Default

```ts
0.5
```

***

### coordinates?

> `optional` **coordinates**: [`LngLatTuple`](/js-sdk-api-reference/shared/lnglattuple)\[]\[]\[]

A multipolygon describing the area that is highlighted.

If `renderHoles` is set to false, only the outer ring of each polygon\
will be rendered, filling in the area inside the highlighted region.


# ImageElementCreate

***

## Properties

### type

> **type**: `"Image"`

***

### coordinates

> **coordinates**: \[`number`, `number`]\[]\[] = `MultiLineStringGeometrySchema.shape.coordinates`

***

### imageUrl

> **imageUrl**: `string`

The URL of the image that is rendered in this element

***

### groupId?

> `optional` **groupId**: `null` | `string`

The ID of the element group that the element belongs to.\
For elements that are not part of a group, this will be null.

***

### name?

> `optional` **name**: `null` | `string`

The element's name. For elements that can show a label or text on\
the map (e.g. a Place or Text element) this is the text that will be shown.

For elements such as Polygons or Paths, the name is what is shown when\
the element is selected by clicking on it.

***

### description?

> `optional` **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it\
is selected.

Note that some elements are not selectable on the map, such as Notes, Text\
and Markers, so their description will not be shown.

***

### attributes?

> `optional` **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not\
part of the element's core data, such as a Place's address or some other\
data.

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by\
the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user,\
which is often used for elements that you don't want the user to edit or move by\
accident.

Elements that were created by the map author (i.e. not during an SDK "session") are\
not editable and have special behaviour depending on their name, description and\
attributes.

#### Default

```ts
"default"
```

***

### opacity?

> `optional` **opacity**: `number`

The opacity of the image, between 0 and 1.

#### Default

```ts
1
```


# ImageElementRead

***

## Properties

### id

> **id**: `string`

The unique identifier for the element.

***

### groupId

> **groupId**: `null` | `string`

The ID of the element group that the element belongs to.\
For elements that are not part of a group, this will be null.

***

### name

> **name**: `null` | `string`

The element's name. For elements that can show a label or text on\
the map (e.g. a Place or Text element) this is the text that will be shown.

For elements such as Polygons or Paths, the name is what is shown when\
the element is selected by clicking on it.

***

### description

> **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it\
is selected.

Note that some elements are not selectable on the map, such as Notes, Text\
and Markers, so their description will not be shown.

***

### attributes

> **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not\
part of the element's core data, such as a Place's address or some other\
data.

***

### type

> **type**: `"Image"`

***

### imageUrl

> **imageUrl**: `string`

The URL of the image that is rendered in this element

***

### opacity

> **opacity**: `number`

The opacity of the image, between 0 and 1.

#### Default

```ts
1
```

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by\
the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user,\
which is often used for elements that you don't want the user to edit or move by\
accident.

Elements that were created by the map author (i.e. not during an SDK "session") are\
not editable and have special behaviour depending on their name, description and\
attributes.

#### Default

```ts
"default"
```


# ImageElementUpdate

***

## Properties

### id

> **id**: `string`

The unique identifier for the element.

***

### type

> **type**: `"Image"`

***

### groupId?

> `optional` **groupId**: `null` | `string`

The ID of the element group that the element belongs to.\
For elements that are not part of a group, this will be null.

***

### name?

> `optional` **name**: `null` | `string`

The element's name. For elements that can show a label or text on\
the map (e.g. a Place or Text element) this is the text that will be shown.

For elements such as Polygons or Paths, the name is what is shown when\
the element is selected by clicking on it.

***

### description?

> `optional` **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it\
is selected.

Note that some elements are not selectable on the map, such as Notes, Text\
and Markers, so their description will not be shown.

***

### attributes?

> `optional` **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not\
part of the element's core data, such as a Place's address or some other\
data.

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by\
the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user,\
which is often used for elements that you don't want the user to edit or move by\
accident.

Elements that were created by the map author (i.e. not during an SDK "session") are\
not editable and have special behaviour depending on their name, description and\
attributes.

#### Default

```ts
"default"
```

***

### coordinates?

> `optional` **coordinates**: \[`number`, `number`]\[]\[] = `MultiLineStringGeometrySchema.shape.coordinates`

***

### imageUrl?

> `optional` **imageUrl**: `string`

The URL of the image that is rendered in this element

***

### opacity?

> `optional` **opacity**: `number`

The opacity of the image, between 0 and 1.

#### Default

```ts
1
```


# LinkElementRead

***

## Properties

### id

> **id**: `string`

The unique identifier for the element.

***

### groupId

> **groupId**: `null` | `string`

The ID of the element group that the element belongs to.\
For elements that are not part of a group, this will be null.

***

### color

> **color**: `string`

The color of the element in some CSS-like format.

#### Example

```typescript
"#ABC123";
"rgb(255, 0, 0)";
"hsl(200, 100%, 50%)";
```

#### Default

```ts
"#C93535"
```

***

### name

> **name**: `null` | `string`

The element's name. For elements that can show a label or text on\
the map (e.g. a Place or Text element) this is the text that will be shown.

For elements such as Polygons or Paths, the name is what is shown when\
the element is selected by clicking on it.

***

### description

> **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it\
is selected.

Note that some elements are not selectable on the map, such as Notes, Text\
and Markers, so their description will not be shown.

***

### attributes

> **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not\
part of the element's core data, such as a Place's address or some other\
data.

***

### type

> **type**: `"Link"`

***

### url

> **url**: `string`

The URL of the link that is rendered in this element.

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by\
the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user,\
which is often used for elements that you don't want the user to edit or move by\
accident.

Elements that were created by the map author (i.e. not during an SDK "session") are\
not editable and have special behaviour depending on their name, description and\
attributes.

#### Default

```ts
"default"
```


# MarkerElementCreate

***

## Properties

### type

> **type**: `"Marker"`

***

### coordinates

> **coordinates**: [`LngLatTuple`](/js-sdk-api-reference/shared/lnglattuple)\[]\[]

***

### groupId?

> `optional` **groupId**: `null` | `string`

The ID of the element group that the element belongs to.\
For elements that are not part of a group, this will be null.

***

### color?

> `optional` **color**: `string`

The color of the element in some CSS-like format.

#### Example

```typescript
"#ABC123";
"rgb(255, 0, 0)";
"hsl(200, 100%, 50%)";
```

#### Default

```ts
"#C93535"
```

***

### name?

> `optional` **name**: `null` | `string`

The element's name. For elements that can show a label or text on\
the map (e.g. a Place or Text element) this is the text that will be shown.

For elements such as Polygons or Paths, the name is what is shown when\
the element is selected by clicking on it.

***

### description?

> `optional` **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it\
is selected.

Note that some elements are not selectable on the map, such as Notes, Text\
and Markers, so their description will not be shown.

***

### attributes?

> `optional` **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not\
part of the element's core data, such as a Place's address or some other\
data.

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by\
the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user,\
which is often used for elements that you don't want the user to edit or move by\
accident.

Elements that were created by the map author (i.e. not during an SDK "session") are\
not editable and have special behaviour depending on their name, description and\
attributes.

#### Default

```ts
"default"
```

***

### opacity?

> `optional` **opacity**: `number`

The opacity of the marker, between 0 and 1.

#### Default

```ts
1
```

***

### size?

> `optional` **size**: `number`

The size of the marker, used in conjunction with the `zoom` to determine\
the actual size of the marker.

#### Default

```ts
10
```

***

### zoom?

> `optional` **zoom**: `number`

The zoom level at which the marker was created. This is combined with\
the `size` to determine the actual size of the marker.

When creating a marker, if you don't supply this value it defaults to\
the current zoom of the map when you call `createElement`.


# MarkerElementRead

***

## Properties

### id

> **id**: `string`

The unique identifier for the element.

***

### groupId

> **groupId**: `null` | `string`

The ID of the element group that the element belongs to.\
For elements that are not part of a group, this will be null.

***

### color

> **color**: `string`

The color of the element in some CSS-like format.

#### Example

```typescript
"#ABC123";
"rgb(255, 0, 0)";
"hsl(200, 100%, 50%)";
```

#### Default

```ts
"#C93535"
```

***

### name

> **name**: `null` | `string`

The element's name. For elements that can show a label or text on\
the map (e.g. a Place or Text element) this is the text that will be shown.

For elements such as Polygons or Paths, the name is what is shown when\
the element is selected by clicking on it.

***

### description

> **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it\
is selected.

Note that some elements are not selectable on the map, such as Notes, Text\
and Markers, so their description will not be shown.

***

### attributes

> **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not\
part of the element's core data, such as a Place's address or some other\
data.

***

### type

> **type**: `"Marker"`

***

### opacity

> **opacity**: `number`

The opacity of the marker, between 0 and 1.

#### Default

```ts
1
```

***

### size

> **size**: `number`

The size of the marker, used in conjunction with the `zoom` to determine\
the actual size of the marker.

#### Default

```ts
10
```

***

### zoom

> **zoom**: `number`

The zoom level at which the marker was created. This is combined with\
the `size` to determine the actual size of the marker.

When creating a marker, if you don't supply this value it defaults to\
the current zoom of the map when you call `createElement`.

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by\
the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user,\
which is often used for elements that you don't want the user to edit or move by\
accident.

Elements that were created by the map author (i.e. not during an SDK "session") are\
not editable and have special behaviour depending on their name, description and\
attributes.

#### Default

```ts
"default"
```


# MarkerElementUpdate

***

## Properties

### id

> **id**: `string`

The unique identifier for the element.

***

### type

> **type**: `"Marker"`

***

### groupId?

> `optional` **groupId**: `null` | `string`

The ID of the element group that the element belongs to.\
For elements that are not part of a group, this will be null.

***

### color?

> `optional` **color**: `string`

The color of the element in some CSS-like format.

#### Example

```typescript
"#ABC123";
"rgb(255, 0, 0)";
"hsl(200, 100%, 50%)";
```

#### Default

```ts
"#C93535"
```

***

### name?

> `optional` **name**: `null` | `string`

The element's name. For elements that can show a label or text on\
the map (e.g. a Place or Text element) this is the text that will be shown.

For elements such as Polygons or Paths, the name is what is shown when\
the element is selected by clicking on it.

***

### description?

> `optional` **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it\
is selected.

Note that some elements are not selectable on the map, such as Notes, Text\
and Markers, so their description will not be shown.

***

### attributes?

> `optional` **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not\
part of the element's core data, such as a Place's address or some other\
data.

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by\
the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user,\
which is often used for elements that you don't want the user to edit or move by\
accident.

Elements that were created by the map author (i.e. not during an SDK "session") are\
not editable and have special behaviour depending on their name, description and\
attributes.

#### Default

```ts
"default"
```

***

### opacity?

> `optional` **opacity**: `number`

The opacity of the marker, between 0 and 1.

#### Default

```ts
1
```

***

### size?

> `optional` **size**: `number`

The size of the marker, used in conjunction with the `zoom` to determine\
the actual size of the marker.

#### Default

```ts
10
```

***

### zoom?

> `optional` **zoom**: `number`

The zoom level at which the marker was created. This is combined with\
the `size` to determine the actual size of the marker.

When creating a marker, if you don't supply this value it defaults to\
the current zoom of the map when you call `createElement`.

***

### coordinates?

> `optional` **coordinates**: [`LngLatTuple`](/js-sdk-api-reference/shared/lnglattuple)\[]\[]


# NoteElementCreate

***

## Properties

### type

> **type**: `"Note"`

***

### text

> **text**: `string`

***

### groupId?

> `optional` **groupId**: `null` | `string`

The ID of the element group that the element belongs to. For elements that are not part of a group, this will be null.

***

### color?

> `optional` **color**: `string`

The color of the element in some CSS-like format.

#### Example

```typescript
"#ABC123";
"rgb(255, 0, 0)";
"hsl(200, 100%, 50%)";
```

#### Default

```ts
"#C93535"
```

***

### description?

> `optional` **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it is selected.

Note that some elements are not selectable on the map, such as Notes, Text and Markers, so their description will not be shown.

***

### attributes?

> `optional` **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not part of the element's core data, such as a Place's address or some other data.

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user, which is often used for elements that you don't want the user to edit or move by accident.

Elements that were created by the map author (i.e. not during an SDK "session") are not editable and have special behaviour depending on their name, description and attributes.

#### Default

```ts
"default"
```

***

### rotation?

> `optional` **rotation**: `number`

The rotation of the element in degrees.

#### Default

```ts
0
```

***

### scale?

> `optional` **scale**: `number`

The relative scale of the element from the default size. This is combined with the `zoom` to determine the actual size of the element.

#### Default

```ts
1
```

***

### zoom?

> `optional` **zoom**: `number`

The zoom level at which the element was created. This is combined with the `scale` to determine the actual size of the element.

When creating an element, if you don't supply this value it defaults to the current zoom of the map when you call `createElement`.

***

### align?

> `optional` **align**: `"center"` | `"left"` | `"right"`

The alignment of the text, either `left`, `center` or `right`.

#### Default

```ts
"center"
```

***

### style?

> `optional` **style**: `"light"` | `"italic"` | `"regular"` | `"caps"`

The style of the text, either `italic`, `light`, `regular` or `caps`.

#### Default

```ts
"regular"
```

***

### widthScale?

> `optional` **widthScale**: `number`

***

### position?

> `optional` **position**: [`LngLatTuple`](/js-sdk-api-reference/shared/lnglattuple)

The geographical position of the center of the note element.

If this is omitted, the note will be placed at the center of the current viewport.


# NoteElementRead

***

## Properties

### id

> **id**: `string`

The unique identifier for the element.

***

### groupId

> **groupId**: `null` | `string`

The ID of the element group that the element belongs to. For elements that are not part of a group, this will be null.

***

### color

> **color**: `string`

The color of the element in some CSS-like format.

#### Example

```typescript
"#ABC123";
"rgb(255, 0, 0)";
"hsl(200, 100%, 50%)";
```

#### Default

```ts
"#C93535"
```

***

### description

> **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it is selected.

Note that some elements are not selectable on the map, such as Notes, Text and Markers, so their description will not be shown.

***

### attributes

> **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not part of the element's core data, such as a Place's address or some other data.

***

### rotation

> **rotation**: `number`

The rotation of the element in degrees.

#### Default

```ts
0
```

***

### scale

> **scale**: `number`

The relative scale of the element from the default size. This is combined with the `zoom` to determine the actual size of the element.

#### Default

```ts
1
```

***

### zoom

> **zoom**: `number`

The zoom level at which the element was created. This is combined with the `scale` to determine the actual size of the element.

When creating an element, if you don't supply this value it defaults to the current zoom of the map when you call `createElement`.

***

### text

> **text**: `string`

The text in the element.

***

### align

> **align**: `"center"` | `"left"` | `"right"`

The alignment of the text, either `left`, `center` or `right`.

#### Default

```ts
"center"
```

***

### style

> **style**: `"light"` | `"italic"` | `"regular"` | `"caps"`

The style of the text, either `italic`, `light`, `regular` or `caps`.

#### Default

```ts
"regular"
```

***

### name

> **name**: `string`

The text shown in the element, which is identical to the `text` property.

#### Remarks

This is added for consistency with other elements that have a `name` property.

***

### type

> **type**: `"Note"`

***

### widthScale

> **widthScale**: `number`

***

### position

> **position**: [`LngLatTuple`](/js-sdk-api-reference/shared/lnglattuple)

The geographical position of the center of the note element.

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user, which is often used for elements that you don't want the user to edit or move by accident.

Elements that were created by the map author (i.e. not during an SDK "session") are not editable and have special behaviour depending on their name, description and attributes.

#### Default

```ts
"default"
```


# NoteElementUpdate

***

## Properties

### id

> **id**: `string`

The unique identifier for the element.

***

### type

> **type**: `"Note"`

***

### groupId?

> `optional` **groupId**: `null` | `string`

The ID of the element group that the element belongs to. For elements that are not part of a group, this will be null.

***

### color?

> `optional` **color**: `string`

The color of the element in some CSS-like format.

#### Example

```typescript
"#ABC123";
"rgb(255, 0, 0)";
"hsl(200, 100%, 50%)";
```

#### Default

```ts
"#C93535"
```

***

### description?

> `optional` **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it is selected.

Note that some elements are not selectable on the map, such as Notes, Text and Markers, so their description will not be shown.

***

### attributes?

> `optional` **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not part of the element's core data, such as a Place's address or some other data.

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user, which is often used for elements that you don't want the user to edit or move by accident.

Elements that were created by the map author (i.e. not during an SDK "session") are not editable and have special behaviour depending on their name, description and attributes.

#### Default

```ts
"default"
```

***

### rotation?

> `optional` **rotation**: `number`

The rotation of the element in degrees.

#### Default

```ts
0
```

***

### scale?

> `optional` **scale**: `number`

The relative scale of the element from the default size. This is combined with the `zoom` to determine the actual size of the element.

#### Default

```ts
1
```

***

### zoom?

> `optional` **zoom**: `number`

The zoom level at which the element was created. This is combined with the `scale` to determine the actual size of the element.

When creating an element, if you don't supply this value it defaults to the current zoom of the map when you call `createElement`.

***

### text?

> `optional` **text**: `string`

The text in the element.

***

### align?

> `optional` **align**: `"center"` | `"left"` | `"right"`

The alignment of the text, either `left`, `center` or `right`.

#### Default

```ts
"center"
```

***

### style?

> `optional` **style**: `"light"` | `"italic"` | `"regular"` | `"caps"`

The style of the text, either `italic`, `light`, `regular` or `caps`.

#### Default

```ts
"regular"
```

***

### widthScale?

> `optional` **widthScale**: `number`

***

### position?

> `optional` **position**: [`LngLatTuple`](/js-sdk-api-reference/shared/lnglattuple)

The geographical position of the center of the note element.


# PathElementCreate

***

## Properties

### type

> **type**: `"Path"`

***

### coordinates

> **coordinates**: [`LngLatTuple`](/js-sdk-api-reference/shared/lnglattuple)\[]\[]

***

### groupId?

> `optional` **groupId**: `null` | `string`

The ID of the element group that the element belongs to.\
For elements that are not part of a group, this will be null.

***

### color?

> `optional` **color**: `string`

The color of the element in some CSS-like format.

#### Example

```typescript
"#ABC123";
"rgb(255, 0, 0)";
"hsl(200, 100%, 50%)";
```

#### Default

```ts
"#C93535"
```

***

### name?

> `optional` **name**: `null` | `string`

The element's name. For elements that can show a label or text on\
the map (e.g. a Place or Text element) this is the text that will be shown.

For elements such as Polygons or Paths, the name is what is shown when\
the element is selected by clicking on it.

***

### description?

> `optional` **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it\
is selected.

Note that some elements are not selectable on the map, such as Notes, Text\
and Markers, so their description will not be shown.

***

### attributes?

> `optional` **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not\
part of the element's core data, such as a Place's address or some other\
data.

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by\
the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user,\
which is often used for elements that you don't want the user to edit or move by\
accident.

Elements that were created by the map author (i.e. not during an SDK "session") are\
not editable and have special behaviour depending on their name, description and\
attributes.

#### Default

```ts
"default"
```

***

### imageUrl?

> `optional` **imageUrl**: `null` | `string`

The URL of an image that has been added to the element.

***

### strokeOpacity?

> `optional` **strokeOpacity**: `number`

A value between 0 and 1 that describes the opacity of the element's stroke.

#### Default

```ts
1
```

***

### strokeWidth?

> `optional` **strokeWidth**: `number`

The width of the element's stroke in pixels.

#### Default

```ts
2
```

***

### strokeStyle?

> `optional` **strokeStyle**: `"solid"` | `"dashed"` | `"dotted"`

The style of the element's stroke.

#### Default

```ts
"solid"
```

***

### distanceMarker?

> `optional` **distanceMarker**: `boolean`

Whether a distance marker is shown at the midpoint of the path.

#### Default

```ts
false
```

***

### routingMode?

> `optional` **routingMode**: `null` | `"driving"` | `"cycling"` | `"walking"` | `"flying"`

Whether this represents a route, and if so, what mode of transport\
is used.

If this is `null`, the path is not considered to be a route, so while it\
can have a `distanceMarker`, it will does not have a start or end cap.

#### Default

```ts
null
```

***

### endCaps?

> `optional` **endCaps**: `boolean`

Whether or not to show Start and End caps on the path. This is\
only available if the `routingMode` is set.

#### Default

```ts
false
```


# PathElementRead

***

## Properties

### id

> **id**: `string`

The unique identifier for the element.

***

### groupId

> **groupId**: `null` | `string`

The ID of the element group that the element belongs to.\
For elements that are not part of a group, this will be null.

***

### color

> **color**: `string`

The color of the element in some CSS-like format.

#### Example

```typescript
"#ABC123";
"rgb(255, 0, 0)";
"hsl(200, 100%, 50%)";
```

#### Default

```ts
"#C93535"
```

***

### name

> **name**: `null` | `string`

The element's name. For elements that can show a label or text on\
the map (e.g. a Place or Text element) this is the text that will be shown.

For elements such as Polygons or Paths, the name is what is shown when\
the element is selected by clicking on it.

***

### description

> **description**: `null` | `string`

Text describing the element, which is shown in an element's popup when it\
is selected.

Note that some elements are not selectable on the map, such as Notes, Text\
and Markers, so their description will not be shown.

***

### attributes

> **attributes**: `Record`<`string`, `unknown`>

A set of key-value pairs that can be used to store arbitrary data about the element.

This is most useful for associating additional data with an element that is not\
part of the element's core data, such as a Place's address or some other\
data.

***

### imageUrl

> **imageUrl**: `null` | `string`

The URL of an image that has been added to the element.

***

### strokeOpacity

> **strokeOpacity**: `number`

A value between 0 and 1 that describes the opacity of the element's stroke.

#### Default

```ts
1
```

***

### strokeWidth

> **strokeWidth**: `number`

The width of the element's stroke in pixels.

#### Default

```ts
2
```

***

### strokeStyle

> **strokeStyle**: `"solid"` | `"dashed"` | `"dotted"`

The style of the element's stroke.

#### Default

```ts
"solid"
```

***

### type

> **type**: `"Path"`

***

### distanceMarker

> **distanceMarker**: `boolean`

Whether a distance marker is shown at the midpoint of the path.

#### Default

```ts
false
```

***

### routingMode

> **routingMode**: `null` | `"driving"` | `"cycling"` | `"walking"` | `"flying"`

Whether this represents a route, and if so, what mode of transport\
is used.

If this is `null`, the path is not considered to be a route, so while it\
can have a `distanceMarker`, it will does not have a start or end cap.

#### Default

```ts
null
```

***

### endCaps

> **endCaps**: `boolean`

Whether or not to show Start and End caps on the path. This is\
only available if the `routingMode` is set.

#### Default

```ts
false
```

***

### interaction?

> `optional` **interaction**: `"default"` | `"locked"`

Whether the element is interactive.

The `default` interaction mode means that the element can be selected and edited by\
the user, if it was created by the SDK or by the user using a tool.

If the interaction mode is `locked`, the element will not be editable by the user,\
which is often used for elements that you don't want the user to edit or move by\
accident.

Elements that were created by the map author (i.e. not during an SDK "session") are\
not editable and have special behaviour depending on their name, description and\
attributes.

#### Default

```ts
"default"
```




---

[Next Page](/llms-full.txt/1)

