Airtable

Lightweight, dependency-free, in-memory Airtable REST API emulator for testing code that uses the real airtable npm client or the Airtable REST API directly.

Default port: 4611

Quick Start

import Airtable from "airtable";
import { AirtableServer } from "./services/airtable/src/server.js";

const server = new AirtableServer(4611);
await server.start();

const base = new Airtable({
  apiKey: "keyParlel",
  endpointUrl: "http://127.0.0.1:4611",
}).base("appParlel");

const created = await base("Tasks").create({ Name: "Test locally" });
const records = await base("Tasks").select({ pageSize: 10 }).all();

await server.stop();

Implemented Operations

Table records, compatible with the airtable npm client's public table methods:

Service endpoints:

Access via MCP / preview URL

Not currently exposed via MCP or preview URL. Use the REST endpoint at http://127.0.0.1:4611.

Surface coverage

Legend: ✅ supported · ◐ accepted-not-enforced · ✓ by design · ⟳ roadmap

FeatureStatusNotes
Bearer token authAny non-empty Authorization: Bearer ... header accepted. Missing auth returns 401.
Legacy api_key query authAccepted for backward compatibility with older airtable client versions. Real Airtable deprecated api_key as of Feb 2024.
In-memory bases and tablesBases/tables created lazily by writes; cleared by reset(). Ephemeral by design.
Record ID formatGenerates rec + 14 alphanumeric base62 chars matching real Airtable ID format.
Record field valuesJSON values stored as-is. Airtable-specific processing (attachments, collaborators, computed fields) not applied.
List/select with paginationpageSize (default 100), offset string, maxRecords. Response shape: { "records": [...], "offset": "..." }.
Field projectionfields[] and fields query params, and fields array in POST body.
Sortingsort[N][field] + sort[N][direction] query format. Legacy sortField/sortDirection also accepted.
filterByFormulaSupports {Field}, {Field} = value, comparisons, SEARCH(), AND(), OR(). Intended for common client-test formulas, not a complete Airtable formula engine.
POST /listRecordsJSON-body list endpoint for long query strings.
Single record CRUDGET, POST, PATCH, PUT, DELETE on /v0/:baseId/:tableName/:recordId.
Batch create (up to 10)POST with { "records": [...] }. Enforces Airtable's 10-record batch limit.
Batch update (up to 10)PATCH with { "records": [{ "id", "fields" }] }.
Batch replace (up to 10)PUT with { "records": [{ "id", "fields" }] }.
Batch delete (up to 10)DELETE with records[] query params.
returnFieldsByFieldIdReal API returns field objects keyed by field ID. Not yet implemented.
typecastReal API performs best-effort data conversion. Not yet implemented.
performUpsertReal API supports upserts via fieldsToMergeOn. Not yet implemented.
cellFormat (json/string)Real API formats cells as user-facing strings when string is set. Not yet implemented.
view filteringReal API filters to records in a specific view. Not yet implemented.
recordMetadata (commentCount)Real API includes comment counts. Not yet implemented.
Rate limiting (429)Real API limits to 5 req/sec/base. Not enforced locally by design.
Metadata / schema APIReal API has GET /v0/meta/bases/:baseId/tables. Not implemented.
PersistenceState is ephemeral by design.
Attachments, collaborators, linksValues preserved as plain field JSON. Airtable-specific processing intentionally unsupported.

Error codes & shapes

All JSON errors use Airtable-style framing:

{
  "error": {
    "type": "NOT_FOUND",
    "message": "Could not find record"
  }
}
StatusTypeWhen
401AUTHENTICATION_REQUIREDMissing Authorization: Bearer ... and missing api_key query param.
404NOT_FOUNDUnknown endpoint path or missing record.
405METHOD_NOT_ALLOWEDHTTP method not supported for the endpoint.
422INVALID_REQUEST_BODYRequired fields, records, or record id missing or malformed.
422INVALID_REQUEST_UNKNOWNMore than 10 records in a batch operation.
500SERVER_ERRORUnexpected server exception.

Manifest

{
  "name": "airtable",
  "version": "0.1",
  "port": 4611,
  "protocol": "http",
  "healthcheck": "/health"
}
<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

AIRTABLE_API_KEY=parlel
AIRTABLE_ENDPOINT_URL=http://parlel-bridge:4611
AIRTABLE_BASE_ID=appParlel
<!-- parlel:testenv:end -->