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

# Airtable

> Client-mode REST API connector for a curated Airtable Web API v0 slice: create records, filterByFormula list, upsert by device id, and webhook create

Technical Preview

## Overview

A client-mode connector for a **curated IoT / low-code** slice of the [Airtable Web API](https://airtable.com/developers/web/api/introduction) under `https://api.airtable.com`:

| Catalog connector event | Entity / path | Notes |
| - | - | - |
| (smoke) | `GET /v0/meta/bases` | Bases visible to the token |
| POST \{table} records | `POST /v0/{baseId}/{tableIdOrName}` | Device publishes state into a table |
| GET \{table}?filterByFormula= | `GET /v0/{baseId}/{tableIdOrName}?filterByFormula=` | URL-encode the formula |
| PATCH \{table} upsert by device id | `PATCH /v0/{baseId}/{tableIdOrName}` | `performUpsert.fieldsToMergeOn` |
| (optional) webhooks | `POST /v0/bases/{baseId}/webhooks` | Client create; inbound ping is separate |

`BASE_ADDRESS` is `https://api.airtable.com` (no trailing slash; endpoints already include `/v0/...`). Topics use `devops/airtable/<object>/<verb>`. Pass `baseId` and `tableIdOrName` on each MQTT payload (prefer table IDs).

> [!NOTE]
>
> Upsert: set `performUpsert.fieldsToMergeOn` (for example `["Device Id"]`). Zero matches create; one match updates; multiple matches fail.

> [!NOTE]
>
> Webhooks: this package ships **client create** only. Airtable POSTs a lightweight ping to `notificationUrl`. Inbound receive would need a separate `ENABLE_SERVER` connector.

> [!NOTE]
>
> Prefer Hub secrets for tokens. This template has not been verified against a live Airtable base. Status remains blocked until an authenticated live smoke succeeds.

## Basic Syntax

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
# Coreflux REST_API template — Airtable curated IoT / low-code slice
# Auth: Bearer {value.json.api_token} (PAT or OAuth)
# BASE_ADDRESS: https://api.airtable.com

DEFINE ROUTE AirtableRestApi WITH TYPE REST_API
    ADD REST_API_CONFIG
        WITH BASE_ADDRESS "https://api.airtable.com"
        WITH ENABLE_CLIENT "true"
        WITH USE_SSL "true"
        WITH REQUEST_TIMEOUT '60'
        WITH DEFAULT_CONTENT_TYPE "application/json"
    ADD EVENT Listbases
        WITH SOURCE_TOPIC "devops/airtable/bases/list"
        WITH DESTINATION_TOPIC "devops/airtable/bases/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v0/meta/bases"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Bearer {value.json.api_token}"
    ADD EVENT Createrecords
        WITH SOURCE_TOPIC "devops/airtable/records/create"
        WITH DESTINATION_TOPIC "devops/airtable/records/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v0/{value.json.baseId}/{value.json.tableIdOrName}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
            ADD "Authorization" "Bearer {value.json.api_token}"
        WITH BODY "{payload}"
    ADD EVENT Listrecords
        WITH SOURCE_TOPIC "devops/airtable/records/list"
        WITH DESTINATION_TOPIC "devops/airtable/records/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v0/{value.json.baseId}/{value.json.tableIdOrName}?filterByFormula={value.json.filterByFormula}&maxRecords={value.json.maxRecords}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Bearer {value.json.api_token}"
    ADD EVENT Upsertrecords
        WITH SOURCE_TOPIC "devops/airtable/records/upsert"
        WITH DESTINATION_TOPIC "devops/airtable/records/upsert/response"
        WITH METHOD "PATCH"
        WITH ENDPOINT "/v0/{value.json.baseId}/{value.json.tableIdOrName}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
            ADD "Authorization" "Bearer {value.json.api_token}"
        WITH BODY "{payload}"
    ADD EVENT Createwebhook
        WITH SOURCE_TOPIC "devops/airtable/webhooks/create"
        WITH DESTINATION_TOPIC "devops/airtable/webhooks/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v0/bases/{value.json.baseId}/webhooks"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
            ADD "Authorization" "Bearer {value.json.api_token}"
        WITH BODY "{payload}"
```

## Events

| Event | Method | Endpoint | Request topic | Response topic |
| - | - | - | - | - |
| Listbases | GET | `/v0/meta/bases` | `devops/airtable/bases/list` | `devops/airtable/bases/list/response` |
| Createrecords | POST | `/v0/{baseId}/{tableIdOrName}` | `devops/airtable/records/create` | `devops/airtable/records/create/response` |
| Listrecords | GET | `/v0/{baseId}/{tableIdOrName}?filterByFormula=&maxRecords=` | `devops/airtable/records/list` | `devops/airtable/records/list/response` |
| Upsertrecords | PATCH | `/v0/{baseId}/{tableIdOrName}` | `devops/airtable/records/upsert` | `devops/airtable/records/upsert/response` |
| Createwebhook | POST | `/v0/bases/{baseId}/webhooks` | `devops/airtable/webhooks/create` | `devops/airtable/webhooks/create/response` |

## Auth

**Personal access token or OAuth 2.0 Bearer — default in the template:**

1. Create a PAT at [airtable.com/create/tokens](https://airtable.com/create/tokens) (or an OAuth app) with scopes `data.records:read`, `data.records:write`, plus `schema.bases:read` for ListBases and `webhook:manage` for CreateWebhook. Add the target base as a resource.
2. Pass `api_token` on the MQTT request payload so headers resolve:
   * `Authorization: Bearer {value.json.api_token}`
3. Store the token in Hub secrets / `GET SECRET "AIRTABLE_TOKEN"`. Never commit real credentials.
4. Pass `baseId` and `tableIdOrName` on each call (prefer table IDs so renames do not break paths).

## Example payloads

**List bases (smoke):**

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{ "api_token": "YOUR_PAT_OR_OAUTH_TOKEN" }
```

**Create records:**

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "api_token": "YOUR_PAT_OR_OAUTH_TOKEN",
  "baseId": "appXXXXXXXXXXXXXX",
  "tableIdOrName": "tblXXXXXXXXXXXXXX",
  "records": [
    {
      "fields": {
        "Device Id": "line-a-sensor-01",
        "Temperature": 72.5,
        "Status": "OK"
      }
    }
  ]
}
```

**List with filterByFormula (URL-encoded):**

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "api_token": "YOUR_PAT_OR_OAUTH_TOKEN",
  "baseId": "appXXXXXXXXXXXXXX",
  "tableIdOrName": "tblXXXXXXXXXXXXXX",
  "filterByFormula": "%7BDevice%20Id%7D%3D%27line-a-sensor-01%27",
  "maxRecords": "50"
}
```

**Upsert by device id:**

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "api_token": "YOUR_PAT_OR_OAUTH_TOKEN",
  "baseId": "appXXXXXXXXXXXXXX",
  "tableIdOrName": "tblXXXXXXXXXXXXXX",
  "performUpsert": { "fieldsToMergeOn": ["Device Id"] },
  "records": [
    {
      "fields": {
        "Device Id": "line-a-sensor-01",
        "Temperature": 73.1,
        "Status": "WARN"
      }
    }
  ]
}
```

**Create webhook:**

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "api_token": "YOUR_PAT_OR_OAUTH_TOKEN",
  "baseId": "appXXXXXXXXXXXXXX",
  "notificationUrl": "https://example.com/airtable-ping",
  "specification": {
    "options": {
      "filters": {
        "dataTypes": ["tableData"],
        "recordChangeScope": "tblXXXXXXXXXXXXXX"
      }
    }
  }
}
```

## Rate limits and notes

* Airtable enforces per-base rate limits; back off on HTTP 429.
* Prefer table IDs over names.
* `filterByFormula` must be URL-encoded; keep URLs under \~16k characters.
* Deployable `.lot` must not include `ADD METADATA` (Hub uses `hub-metadata.lot` separately).

## Sources

* [API introduction](https://airtable.com/developers/web/api/introduction)
* [Authentication](https://airtable.com/developers/web/api/authentication)
* [Create records](https://airtable.com/developers/web/api/create-records)
* [List records](https://airtable.com/developers/web/api/list-records)
* [Update multiple records (upsert)](https://airtable.com/developers/web/api/update-multiple-records)
* [Create a webhook](https://airtable.com/developers/web/api/create-a-webhook)
* [Webhooks overview](https://airtable.com/developers/web/api/webhooks-overview)

## Related

* [REST API Connectors](/latest/lot-language/routes/rest-api-routes)
* [REST API Apps](/latest/lot-language/routes/rest-api-apps)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.