Using the Airtable API from an AI Agent
Someone renames a field in the UI and your agent breaks with an error saying the field does not exist, while you are looking right at it. Airtable's schema drift problem, solved.
Here is a bug report you will eventually receive. The agent stopped working. The error says the field does not exist. You open the base and the field is right there, with the same name it has always had.
Nobody is lying. Someone on the ops team deleted that column and made a new one with the same name, type and position, because that felt like the tidy way to change its settings. To Airtable, that is a different field with a different ID, and anything holding the old ID now gets a 422 telling it the field is unknown.
Airtable is the friendliest database your non-technical colleagues have ever used, which is exactly why it is the least stable schema your agent will ever depend on. People change it, constantly, with no idea anything is reading it.
Field IDs vs field names: pick your poison
You can reference fields two ways, and both break differently.
- By name. Survives a delete-and-recreate, because the new field has the same name. Breaks the moment someone renames "Status" to "Stage."
- By ID. Survives renames, which is the common case. Breaks on delete-and-recreate, with the confusing
422 UNKNOWN_FIELD_NAMEabove.
For agents we lean towards IDs, because renames happen far more often than recreations, and a rename is completely invisible to everyone involved. Nobody thinks "I should tell the engineering team" when they tidy up a column header.
The real answer is to stop hardcoding either one. Read the base schema at startup, build a name-to-ID map, and validate it before you do anything. If a field you need is missing, fail loudly with a message naming the field rather than letting the agent improvise. Airtable's own guidance also helps: change field settings in place rather than deleting and recreating, which preserves the ID. That is worth telling whoever owns the base.
This is the Airtable-specific version of a general problem: a human reads the changelog, an agent keeps calling the old thing until it 404s. We wrote that up in API versioning strategy when AI agents are your consumers, and the tool-selection cousin of it is in why your AI agent calls the wrong API.
The rate limit that punishes agents specifically
Airtable allows 5 requests per second per base. That is low, and the penalty is the unusual part: exceed it and you get a 429, and then you need to wait 30 seconds before requests succeed again.
Thirty seconds. Not a few hundred milliseconds of backoff. An agent that fires a burst of requests, hits the wall, and retries naively will spend most of its life in that penalty box. Worse, a single request can trigger it if it arrives just after someone else's burst against the same base, because the limit is per base and your agent is probably not the only thing talking to it.
Rate-limit errors get exponential backoff with jitter automatically, which is exactly the recommended handling here, and the retries guide covers the behaviour. But the design fix is yours: batch aggressively and do not let the agent loop one record at a time.
Batch create and batch update take up to 10 records per request, so a 100-record update is 10 calls rather than 100, which keeps you inside 5 per second with room to spare. Oversized payloads come back as 413 Request Entity Too Large.
The monthly cap nobody notices until the 28th
Separate from the per-second limit, there is a monthly call allowance tied to your plan: 1,000 calls a month on Free, 100,000 on Team, and unlimited on Business and Enterprise. Blow through it and you get 429 PUBLIC_API_BILLING_LIMIT_EXCEEDED, which does not reset until the first of the next month.
A thousand calls is nothing for an agent. A single enthusiastic prototype can spend that in an afternoon, and then your integration is simply dead for three weeks with no amount of retrying able to fix it. If you are building on Free, know that number before you start, and treat it as a hard budget rather than a rate limit.
One useful tip from Airtable's own troubleshooting: the allowance follows the workspace the base lives in, so moving a base into your highest-paid workspace raises its ceiling.
Setting it up
npx swytchcode
swy login
swy init --mode=sandbox
swy get airtable
swy auth connect airtable # personal access token or OAuth
swy auth status
# read the schema first; everything else depends on it
swy discover "get base schema" --project airtable
swy discover "list records" --project airtable
swy discover "update records" --project airtable
swy list methods airtable
swy info <canonical_id>
swy add method <canonical_id> # schema read, list, batch update. Not delete.Leave the delete methods out of the allowlist. Airtable has no undo through the API, and a confused agent deleting records from a table your ops team depends on is a genuinely bad afternoon. If the method is not added, there is no instruction that can reach it. Scope every discover with --project airtable, and run swy get before swy info.
Your token goes to a local store outside the project, encrypted at rest, and is injected at call time, so there is no AIRTABLE_API_KEY for the model to read or a trace to leak. Worth caring about, because an Airtable token often reaches a lot of operational data. See the authentication docs and scope the token to the specific bases it needs.
The pattern that survives contact with real users:
import { exec } from "@swytchcode/runtime";
// 1. Read the schema ONCE per run and build the map.
const schema = await exec("<airtable get-schema canonical_id>", {
params: { baseId: BASE_ID }
});
const fields = Object.fromEntries(
schema.tables.find(t => t.name === "Orders").fields.map(f => [f.name, f.id])
);
// 2. Fail loudly if what you need is gone, instead of improvising.
for (const needed of ["Status", "Owner", "Notes"]) {
if (!fields[needed]) throw new Error(`Airtable field missing: ${needed}`);
}
// 3. Batch in tens, referencing by field ID.
for (const chunk of chunksOf(updates, 10)) {
await exec("<airtable update-records canonical_id>", {
params: { baseId: BASE_ID, tableId: TABLE_ID },
body: {
records: chunk.map(u => ({
id: u.recordId,
fields: { [fields.Status]: u.status }
})),
returnFieldsByFieldId: true
},
idempotencyKey: `orders-sync:${runId}:${chunk[0].recordId}`
});
}The returnFieldsByFieldId flag is worth knowing: it makes responses use field IDs rather than names, so your parsing does not break on a rename either.
Idempotency and the duplicate row
Creating records is not idempotent, and Airtable rows are easy to duplicate because there is usually no unique constraint stopping you. The familiar sequence: the request lands, records are created, the response is lost, the agent retries, and now your ops team has two of everything and no obvious way to tell which is real.
Writes are only auto-retried when an idempotency key is set, so an unprotected batch create fails visibly rather than silently doubling. Derive the key from the source record and the run, as above. And because there is no database constraint to save you, add a cheap guard: keep an external ID column, and look up before you create. Background in API idempotency: the complete guide and mechanics in the idempotency docs.
Policies: protect the tables people actually depend on
In Airtable the useful rule is usually about which table, not which amount. Writing to a staging table is fine; writing to the table your finance team reports from is a different matter.
{
"id": "protect-the-master-table",
"target": ["<airtable update-records canonical_id>"],
"when": { "field": "tableId", "operator": "==", "value": "<finance-table-id>" },
"action": {
"type": "REQUIRES_APPROVAL",
"message": "Agent wants to write to the finance master table"
},
"approval_timeout": "4h"
}Add a second rule capping batch size so "clean up the statuses" cannot rewrite 2,000 rows in one run. Held calls go to Slack or Telegram and fail closed. Schema in the policy reference, approval setup in the human approval docs, and the reasoning for keeping rules in git in policy-as-code for AI agents.
From Claude Code or Cursor
Airtable over MCP is genuinely handy because so much operational context lives in these bases: you are writing code and you want to know what the ops tracker says, without opening another tab. Connect through the MCP server and the allowlist travels with it, so a read-only setup stays read-only even in an editor. Setup in the MCP guide, reference in the MCP docs, and skills.md if you want a coding agent to wire it up.
What you can build
- Document to record entry. The job Airtable users most want automated. Read an invoice, a form or an email, extract the fields, and create one row with an external ID so reruns cannot duplicate it. Batch in tens.
- A tracker status sweep. Read a project or content tracker, work out which rows are stale or inconsistent, and post a digest rather than silently fixing them. Read-only first; people trust it faster that way.
- A schema-drift watchdog. Read the base schema on a schedule, compare it against the last snapshot, and alert when a field is renamed, retyped or recreated. This is the agent that stops the other agents breaking, and it is about twenty lines.
- Cross-tool sync with a guard. Keep an Airtable view aligned with Notion or a support queue, writing only into a designated sync table so a bug cannot touch the master data.
- A reporting agent. Pull a view, compute the numbers properly in code rather than asking a model to do arithmetic, and write a weekly summary. Reads only, and it removes a recurring half hour from somebody's Friday.
Things we got wrong
We hardcoded field names, because it was quicker and the names looked permanent. Three weeks later someone renamed two columns while tidying a view, and the agent started writing to nothing in particular.
We also treated the 429 like a normal rate limit and retried after a second. With a 30-second penalty, that means every retry fails too, which looks like a much bigger outage than it is. Respect the wait.
And we tested on a Free workspace and ran out of monthly calls on a Tuesday, which is a confusing failure because it presents as a 429 that backoff cannot solve. Check swy audit network for your actual call volume before you blame the code.
FAQ
Field IDs or field names for an agent?
IDs, resolved at runtime from the schema rather than hardcoded, with a validation step that fails loudly when something is missing. That survives renames, which are common, and turns recreations into a clear error rather than silent misbehaviour.
Why did I get UNKNOWN_FIELD_NAME for a field I can see?
Someone deleted and recreated it, so the name is the same and the ID is not. Re-read the schema, update whatever cached the old ID, and ask your base owners to change field settings in place in future.
Is Airtable a sensible system of record for agent work?
For coordination and ops, absolutely, and the fact that humans can edit it is the feature. For anything where correctness is critical and schema stability matters, treat it as a view onto a real database rather than the source of truth. The Notion guide makes a similar point about databases there.
How do I stop an agent rewriting hundreds of rows?
A policy on batch size plus the per-request limit of 10 records gives you two natural brakes, and leaving delete methods out of the allowlist removes the worst case entirely. swy audit policy then shows you what it tried to do and was stopped from doing, which is usually an education about your own prompts. More in API access control for AI agents.
Related reading
More content
How to Debug an AI Agent That Called the Wrong API
Working backwards from the response your agent got to the tool-selection decision that caused it, which is where most of the real debugging time goes.
iPaaS Was Built for Integrations, Not Agents
Deterministic trigger-action versus non-deterministic tool choice, and why the governance model that works for one doesn't transfer cleanly to the other.
FreshDesk API for AI Support Agents
A note is private, a reply is an email you cannot recall, and they are one field apart. Freshdesk auth, the magic status numbers, and how to build an agent that drafts.
