API mocks
Airtable
A mock compatible with the Airtable Web API. Your app reads and writes records, filters them with formulas and pages through them as it does with Airtable, with Airtable's errors, its rate limit and its webhooks. Each mock is one base.
Testlie is not affiliated with, endorsed by or sponsored by Airtable. The name is a trademark of its owner, used here only to say which API this mock is compatible with.
Connect
Create an Airtable mock under API mocks → Business tools. Use its base URL in place of https://api.airtable.com, its base id and its personal access token (Authorization: Bearer pat…).
import Airtable from "airtable";
const base = new Airtable({ apiKey: process.env.AIRTABLE_TOKEN, endpointUrl: "<base URL>" }).base(process.env.AIRTABLE_BASE_ID);
const records = await base("Tasks").select({ filterByFormula: "{Status} = 'Todo'", sort: [{ field: "Due" }] }).all();A missing or wrong token answers 401 AUTHENTICATION_REQUIRED; another base, or a table that does not exist, answers 403 INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND.
Tables
A new base has a Tasks table with Name, Notes, Status (single select: Todo, In progress, Done), Tags (multiple select), Due (date), Done (checkbox), Count (number) and Email. Add tables and fields with the metadata API:
| Endpoint | |
|---|---|
GET /v0/meta/bases |
The mock's base |
GET /v0/meta/bases/{baseId}/tables |
Tables with fields and views |
POST /v0/meta/bases/{baseId}/tables |
Create a table |
PATCH /v0/meta/bases/{baseId}/tables/{tableId} |
Rename or describe a table |
POST /v0/meta/bases/{baseId}/tables/{tableId}/fields |
Add a field |
PATCH /v0/meta/bases/{baseId}/tables/{tableId}/fields/{fieldId} |
Rename or describe a field |
Tables are addressed by id or name.
Records
| Endpoint | |
|---|---|
GET /v0/{baseId}/{table} |
List: filterByFormula, sort[0][field]/[direction], fields[], view, maxRecords, pageSize (up to 100), offset, returnFieldsByFieldId |
POST /v0/{baseId}/{table}/listRecords |
The same with a JSON body |
GET /v0/{baseId}/{table}/{recordId} |
One record |
POST /v0/{baseId}/{table} |
Create one record (fields) or up to 10 (records), with typecast |
PATCH / PUT /v0/{baseId}/{table} |
Update or replace up to 10 records; performUpsert.fieldsToMergeOn upserts and reports createdRecords and updatedRecords |
PATCH / PUT /v0/{baseId}/{table}/{recordId} |
Update or replace one record |
DELETE /v0/{baseId}/{table}?records[]=... |
Delete up to 10 records |
DELETE /v0/{baseId}/{table}/{recordId} |
Delete one record |
Values are checked against the field type (INVALID_VALUE_FOR_COLUMN, UNKNOWN_FIELD_NAME, INVALID_MULTIPLE_CHOICE_OPTIONS for a new select option without typecast). Empty values are left out of fields, like Airtable. Pages come with an offset until the last one; a bad offset answers LIST_RECORDS_ITERATOR_NOT_AVAILABLE.
Formulas
filterByFormula supports field references ({Name}), strings, numbers, &, arithmetic and comparisons, and the functions AND, OR, XOR, NOT, IF, SWITCH, TRUE, FALSE, BLANK, LEN, LOWER, UPPER, TRIM, T, FIND, SEARCH, CONCATENATE, LEFT, RIGHT, MID, SUBSTITUTE, VALUE, REGEX_MATCH, ARRAYJOIN, ROUND, ABS, MIN, MAX, SUM, RECORD_ID, CREATED_TIME, NOW, TODAY, IS_BEFORE, IS_AFTER, IS_SAME, DATETIME_DIFF, DATEADD. Unknown fields or functions answer 422 INVALID_FILTER_BY_FORMULA.
Rate limit
Like Airtable, a base takes 5 requests per second. The next request answers 429 RATE_LIMIT_REACHED, and so does every request for the following 30 seconds. airtable.js retries 429s by itself; space out bursts in your tests. The 429 rate limit scenario makes every call fail this way.
Webhooks
POST /v0/bases/{baseId}/webhooks with a notificationUrl and a specification (options.filters.dataTypes: ["tableData"], optionally recordChangeScope and changeTypes) returns the webhook id, its macSecretBase64 and expirationTime (7 days; POST .../webhooks/{id}/refresh extends it). Each change to records sends a ping to the notification URL:
{ "base": { "id": "app…" }, "webhook": { "id": "ach…" }, "timestamp": "2026-10-18T12:00:00.000Z" }signed with X-Airtable-Content-MAC: hmac-sha256=<hex HMAC-SHA256 of the body with the decoded MAC secret>. Fetch the changes from GET .../webhooks/{id}/payloads?cursor= (createdRecordsById, changedRecordsById with current and previous values, destroyedRecordIds). GET .../webhooks lists them and DELETE .../webhooks/{id} removes one.
Limits
A mock keeps up to 1,000 records per table (ROW_OR_TABLE_LIMIT_EXCEEDED beyond that). Records and other objects the mock keeps follow your plan's retention (3 days on Free) and are then deleted, so recreate fixtures in your tests rather than relying on them; see API mocks. Attachments, comments, views other than the grid view, cellFormat=string rendering and OAuth tokens are not supported.