Skip to content

Airtable Sensor

What it ingests

Record activity from an Airtable base — rows and their comments — optionally scoped to specific tables. Airtable is the structured-table counterpart to Notion for orgs whose source of truth lives in Airtable rather than a wiki.

This is Airtable alone. Notion databases are a different product with a different API — use the Notion sensor, not this one.

Credential

References an airtable_pat credential by id. v1 is a personal access token (pat…). Airtable is a single cloud API, so there is no site URL beside the token.

bash
curl -sS -X POST https://your-corveil-host/api/credentials \
  -H "Authorization: Bearer <admin>" -H "Content-Type: application/json" \
  -d '{"type":"airtable_pat","name":"acme-airtable","secret":"pat..."}'

OAuth (a connected Airtable app that users install) is out of v1. The retired 17-character API keys will not authenticate.

Config fields

KeyRequiredNotes
base_idyesAirtable base ID (app…). Paste an ID or an airtable.com URL — the app id is extracted either way.
table_idsnoComma-separated table IDs (tbl…) or names. Blank ingests every table in the base the token can read.

Provider-side setup

  1. Open airtable.com/create/tokens and create a personal access token.
  2. Grant scopes: data.records:read, data.recordComments:read, schema.bases:read. Restrict the token to the base you want ingested.
  3. Copy the token (pat…) into a Corveil airtable_pat credential.
  4. Copy the base ID from the base URL (https://airtable.com/app…/…).

A token that cannot read the configured base fails Test connection with an auth error. A token that can read records but was not granted data.recordComments:read still ingests records; comments on those records are skipped until the scope is added.

Wire-up

POST /api/sensors

json
{
  "sensor_type": "airtable",
  "name": "Acme Airtable",
  "config": {
    "base_id": "appXXXXXXXXXXXXXX",
    "table_ids": "tblXXXXXXXXXXXXXX"
  },
  "credentials": { "api_token": "<airtable_pat credential id>" }
}

Leave table_ids empty to ingest every table in the base.

Verify

Run Test connection to confirm the token can read the base schema (GET /v0/meta/bases/{baseId}/tables). Once polling, created and updated records appear as document events, and their comments as conversation events.

Notes

Entity naming. Airtable titles are unique only within a table, so records are named Table/Title (e.g. Tasks/Ship Airtable sensor). The table itself lands as a Project (the Notion database analog).

Occurred-at semantics. A newly created record is stamped with createdTime (metadata). An update is stamped as an ingest-fallback occurred-at (eligible for title-date inference) because Airtable's list API does not always return last-modified unless the table has that field. Comments are stamped metadata: a comment's timestamp really is when it was written.

Incremental filter. Polls ask Airtable for records whose LAST_MODIFIED_TIME() is after the per-table watermark. If a table rejects that formula (computed-only fields), the sensor falls back to CREATED_TIME() for that table — updates on existing rows will not be seen until the row is recreated or the formula starts working. Comments on a touched record are emitted only when they fall in that same window, so a field edit does not re-ingest the row's entire comment history.

First poll. A new source seeds its watermark to "now" and emits nothing on the first tick, so adding a source does not drag in the entire base. Use backfill to ingest history.

Burst drain. A poll that hits the page budget persists a continuation cursor (table + offset) and resumes on the next tick rather than skipping the middle of a burst.

Rate limit. Airtable allows 5 requests per second per base. The sensor paces at 220ms and does not advance the cursor on HTTP 429.