airtable skill
Airtable REST API via curl. Records CRUD, filters, upserts.
Is the airtable skill safe?
Clean: nothing in its files matched our rules. We read 1 file in the folder on 2026-09-28.
No findings.
Install the airtable skill
A skill is a folder. Copy it into your agent's skills folder and the agent loads it when the task matches its description.
git clone --depth 1 https://github.com/kevinnft/ai-agent-skills.git /tmp/ai-agent-skills mkdir -p ~/.claude/skills cp -r /tmp/ai-agent-skills/skills/productivity/airtable ~/.claude/skills/airtable
In the Claude apps, zip the folder and upload it from the Skills settings. The folder on GitHub
The instructions your agent would load
SKILL.md as published, without the frontmatter. Read it on GitHub
Airtable — Bases, Tables & Records
Work with Airtable's REST API directly via curl using the terminal tool. No MCP server, no OAuth flow, no Python SDK — just curl and a personal access token.
Prerequisites
- Create a Personal Access Token (PAT) at https://airtable.com/create/tokens (tokens start with pat...).
- Grant these scopes (minimum):
- data.records:read — read rows
- data.records:write — create / update / delete rows
- schema.bases:read — list bases and tables
- Important: in the same token UI, add each base you want to access to the token's Access list. PATs are scoped per-base — a valid token on the wrong base returns 403.
- Store the token in ~/.hermes/.env (or via hermes setup):
AIRTABLE_API_KEY=pat_your_token_hereNote: legacy key... API keys were deprecated Feb 2024. Only PATs and OAuth tokens work now.
API Basics
- Endpoint: https://api.airtable.com/v0
- Auth header: Authorization: Bearer $AIRTABLEAPIKEY
- All requests use JSON (Content-Type: application/json for any POST/PATCH/PUT body).
- Object IDs: bases app..., tables tbl..., records rec..., fields fld.... IDs never change; names can. Prefer IDs in automations.
- Rate limit: 5 requests/sec/base. 429 → back off. Burst on a single base will be throttled.
Base curl pattern:
curl -s "https://api.airtable.com/v0/$BASE_ID/$TABLE?maxRecords=5" \
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.tool-s suppresses curl's progress bar — keep it set for every call so the tool output stays clean for Hermes. Pipe through python3 -m json.tool (always present) or jq (if installed) for readable JSON.
Field Types (request body shapes)
Pass "typecast": true at the top level of a create/update body to let Airtable auto-coerce values (e.g. create a new select option on the fly, convert "42" → 42).
Common Queries
List bases the token can see
curl -s "https://api.airtable.com/v0/meta/bases" \
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.toolList tables + schema for a base
curl -s "https://api.airtable.com/v0/meta/bases/$BASE_ID/tables" \
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.toolUse this BEFORE mutating — confirms exact field names and IDs, surfaces options.choices for select fields, and shows primary-field names.
List records (first 10)
curl -s "https://api.airtable.com/v0/$BASE_ID/$TABLE?maxRecords=10" \
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.toolGet a single record
curl -s "https://api.airtable.com/v0/$BASE_ID/$TABLE/$RECORD_ID" \
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.toolFilter records (filterByFormula)
Airtable formulas must be URL-encoded. Let Python stdlib do it — never hand-encode:
FORMULA="{Status}='Todo'"
ENC=$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$FORMULA")
curl -s "https://api.airtable.com/v0/$BASE_ID/$TABLE?filterByFormula=$ENC&maxRecords=20" \
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.toolUseful formula patterns:
- Exact match: {Email}='user@example.com'
- Contains: FIND('bug', LOWER({Title}))
- Multiple conditions: AND({Status}='Todo', {Priority}='High')
- Or: OR({Owner}='alice', {Owner}='bob')
- Not empty: NOT({Assignee}='')
- Date comparison: IS_AFTER({Due}, TODAY())
Sort + select specific fields
curl -s "https://api.airtable.com/v0/$BASE_ID/$TABLE?sort%5B0%5D%5Bfield%5D=Priority&sort%5B0%5D%5Bdirection%5D=asc&fields%5B%5D=Name&fields%5B%5D=Status" \
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.toolSquare brackets in query params MUST be URL-encoded (%5B / %5D).
Use a named view
curl -s "https://api.airtable.com/v0/$BASE_ID/$TABLE?view=Grid%20view&maxRecords=50" \
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.toolViews apply their saved filter + sort server-side.
Common Mutations
Create a record
curl -s -X POST "https://api.airtable.com/v0/$BASE_ID/$TABLE" \
-H "Authorization: Bearer $AIRTABLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"fields":{"Name":"New task","Status":"Todo","Priority":"High"}}' | python3 -m json.toolCreate up to 10 records in one call
curl -s -X POST "https://api.airtable.com/v0/$BASE_ID/$TABLE" \
-H "Authorization: Bearer $AIRTABLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"typecast": true,
"records": [
{"fields": {"Name": "Task A", "Status": "Todo"}},
{"fields": {"Name": "Task B", "Status": "In progress"}}
]
}' | python3 -m json.toolBatch endpoints are capped at 10 records per request. For larger inserts, loop in batches of 10 with a short sleep to respect 5 req/sec/base.
Update a record (PATCH — merges, preserves unchanged fields)
curl -s -X PATCH "https://api.airtable.com/v0/$BASE_ID/$TABLE/$RECORD_ID" \
-H "Authorization: Bearer $AIRTABLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"fields":{"Status":"Done"}}' | python3 -m json.toolUpsert by a merge field (no ID needed)
curl -s -X PATCH "https://api.airtable.com/v0/$BASE_ID/$TABLE" \
-H "Authorization: Bearer $AIRTABLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"performUpsert": {"fieldsToMergeOn": ["Email"]},
"records": [
{"fields": {"Email": "user@example.com", "Status": "Active"}}
]
}' | python3 -m json.toolperformUpsert creates records whose merge-field values are new, patches records whose merge-field values already exist. Great for idempotent syncs.
Delete a record
curl -s -X DELETE "https://api.airtable.com/v0/$BASE_ID/$TABLE/$RECORD_ID" \
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.toolDelete up to 10 records in one call
curl -s -X DELETE "https://api.airtable.com/v0/$BASE_ID/$TABLE?records%5B%5D=rec1&records%5B%5D=rec2" \
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.toolPagination
List endpoints return at most 100 records per page. If the response includes "offset": "...", pass it back on the next call. Loop until the field is absent:
OFFSET=""
while :; do
URL="https://api.airtable.com/v0/$BASE_ID/$TABLE?pageSize=100"
[ -n "$OFFSET" ] && URL="$URL&offset=$OFFSET"
RESP=$(curl -s "$URL" -H "Authorization: Bearer $AIRTABLE_API_KEY")
echo "$RESP" | python3 -c 'import json,sys; d=json.load(sys.stdin); [print(r["id"], r["fields"].get("Name","")) for r in d["records"]]'
OFFSET=$(echo "$RESP" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("offset",""))')
[ -z "$OFFSET" ] && break
doneTypical Hermes Workflow
- Confirm auth. curl -s -o /dev/null -w "%{httpcode}\n" https://api.airtable.com/v0/meta/bases -H "Authorization: Bearer $AIRTABLEAPI_KEY" — expect 200.
- Find the base. List bases (step above) OR ask the user for the app... ID directly if the token lacks schema.bases:read.
- Inspect the schema. GET /v0/meta/bases/$BASE_ID/tables — cache the exact field names and primary-field name locally in the session before mutating anything.
- Read before you write. For "update X where Y", filterByFormula first to resolve the rec... ID, then PATCH /v0/$BASEID/$TABLE/$RECORDID. Never guess record IDs.
- Batch writes. Combine related creates into one 10-record POST to stay under the 5 req/sec budget.
- Destructive ops. Deletions can't be undone via API. If the user says "delete all Xs", echo back the filter + record count and confirm before firing.
Pitfalls
- filterByFormula MUST be URL-encoded. Field names with spaces or non-ASCII also need encoding ({My Field} → %7BMy%20Field%7D). Use Python stdlib (pattern above) — never hand-escape.
- Empty fields are omitted from responses. A missing "Assignee" key doesn't mean the field doesn't exist — it means this record's value is empty. Check the schema (step 3) before concluding a field is missing.
- PATCH vs PUT. PATCH merges supplied fields into the record. PUT replaces the record entirely and clears any field you didn't include. Default to PATCH.
- Single-select options must exist. Writing "Status": "Shipping" when Shipping isn't in the field's option list errors with INVALIDMULTIPLECHOICE_OPTIONS unless you pass "typecast": true (which auto-creates the option).
- Per-base token scoping. A 403 on one base while another works means the token's Access list doesn't include that base — not a scope or auth issue. Send the user to https://airtable.com/create/tokens to grant it.
- Rate limits are per base, not per token. 5 req/sec on baseA and 5 req/sec on baseB is fine; 6 req/sec on baseA alone will throttle. Monitor the Retry-After header on 429.
Important Notes for Hermes
- Always use the terminal tool with curl. Do NOT use webextract (it can't send auth headers) or browsernavigate (needs UI auth and is slow).
- AIRTABLEAPIKEY flows from ~/.hermes/.env into the subprocess automatically when this skill is loaded — no need to re-export it before each curl call.
- Escape curly braces in formulas carefully. In a heredoc body, {Status} is literal. In a shell argument, {Status} is safe outside {...} brace-expansion context — but pass dynamic strings through python3 urllib.parse.quote before splicing into a URL.
- Pretty-print with python3 -m json.tool (always present) rather than jq (optional). Only reach for jq when you need filtering/projection.
- Pagination is per-page, not global. Airtable's 100-record cap is a hard limit; there is no way to bump it. Loop with offset until the field is absent.
- Read the errors array on non-2xx responses — Airtable returns structured error codes like AUTHENTICATIONREQUIRED, INVALIDPERMISSIONS, MODELIDNOTFOUND, INVALIDMULTIPLECHOICEOPTIONS that tell you exactly what's wrong.
More skills from kevinnft/ai-agent-skills
- Aaddyosmani-tddDrives development with tests. Use when implementing any logic, fixing any bug, or changing any behavior. Use when you need to prove that code works, when a bug report arrives, or when you're about to modify existing functionality.
- Aapi-and-interface-designGuides stable API and interface design. Use when designing APIs, module boundaries, or any public interface. Use when creating REST or GraphQL endpoints, defining type contracts between modules, or establishing boundaries between frontend and backend.
- Aapi-monitoring-botsBuild monitoring bots that poll APIs and send notifications on state changes (new listings, price alerts, status updates)
- Aapple-notesManage Apple Notes via memo CLI: create, search, edit.
- Aapple-remindersApple Reminders via remindctl: add, list, complete.
- Aarchitecture-diagramDark-themed SVG architecture/cloud/infra diagrams as HTML.
- AarxivSearch arXiv papers by keyword, author, category, or ID.
- Aascii-artASCII art: pyfiglet, cowsay, boxes, image-to-ascii.
- Aascii-videoASCII video: convert video/audio to colored ASCII MP4/GIF.
- AaudiocraftAudioCraft: MusicGen text-to-music, AudioGen text-to-sound.
- CaxolotlAxolotl: YAML LLM fine-tuning (LoRA, DPO, GRPO).
- Abaoyu-comicKnowledge comics (知识漫画): educational, biography, tutorial.