agentsclimarketplace

Airtable

Skill furkangonel/cowrangler/bundled_skills/productivity/airtable

Autonomous terminal AI agent for workflows and feasible project procedures. Co-Worker Co-Wrangler πŸ™

Install
npx -y skills add furkangonel/cowrangler --skill airtable

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Airtable base management β€” records, views, and formulas via REST API.

SKILL.md

9.7 KB, as published. Nobody here has run it

Airtable REST API SOP

Read, create, update, and delete Airtable records. Covers auth, filtering, pagination, batch operations, and field type reference.

When to Use

  • User wants to read records from an Airtable base
  • User wants to create or update Airtable records
  • User wants to filter or sort records by field values
  • User wants to automate data entry or sync into Airtable
  • User wants to delete or patch records in bulk

Part 1 β€” Auth Setup

1. Get a Personal Access Token

  1. Go to https://airtable.com/create/tokens
  2. Click Create new token
  3. Name it, add scopes: data.records:read, data.records:write, schema.bases:read
  4. Add bases or grant access to all bases
  5. Copy the token (shown only once)
export AIRTABLE_TOKEN="patXXXXXXXXXXXXXX.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

Or store in ~/.cowrangler/credentials.env:

AIRTABLE_TOKEN=patXXXXXXXXXXXXXX.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

2. Find Your Base and Table IDs

Base ID is in the URL: https://airtable.com/appXXXXXXXXXXXXXX/... β†’ appXXXXXXXXXXXXXX

Table ID or name β€” use the table name directly in the URL (URL-encoded) or get the ID from the metadata API:

AIRTABLE_TOKEN="${AIRTABLE_TOKEN:-$(grep '^AIRTABLE_TOKEN=' ~/.cowrangler/credentials.env 2>/dev/null | cut -d= -f2 | tr -d '\n\r')}"
BASE_ID="appXXXXXXXXXXXXXX"

# List all tables in a base
curl -s "https://api.airtable.com/v0/meta/bases/$BASE_ID/tables" \
  -H "Authorization: Bearer $AIRTABLE_TOKEN" \
  | python3 -c "
import sys, json
data = json.load(sys.stdin)
for t in data['tables']:
    print(f\"{t['id']}  {t['name']}\")
"

Shell Helper

airtable_get() {
  # Usage: airtable_get "appXXX/TableName?filterByFormula=..."
  curl -s "https://api.airtable.com/v0/$1" \
    -H "Authorization: Bearer $AIRTABLE_TOKEN"
}

airtable_post() {
  # Usage: airtable_post "appXXX/TableName" '{"records":[...]}'
  curl -s -X POST "https://api.airtable.com/v0/$1" \
    -H "Authorization: Bearer $AIRTABLE_TOKEN" \
    -H "Content-Type: application/json" \
    -d "$2"
}

airtable_patch() {
  # Usage: airtable_patch "appXXX/TableName" '{"records":[...]}'
  curl -s -X PATCH "https://api.airtable.com/v0/$1" \
    -H "Authorization: Bearer $AIRTABLE_TOKEN" \
    -H "Content-Type: application/json" \
    -d "$2"
}

Part 2 β€” Reading Records

List Records (basic)

import os, json, urllib.request, urllib.parse

TOKEN   = os.environ["AIRTABLE_TOKEN"]
BASE_ID = "appXXXXXXXXXXXXXX"
TABLE   = "Tasks"  # table name or ID

def airtable_request(method, path, body=None):
    url = f"https://api.airtable.com/v0/{BASE_ID}/{urllib.parse.quote(path)}"
    headers = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
    data = json.dumps(body).encode() if body else None
    req = urllib.request.Request(url, data=data, headers=headers, method=method)
    with urllib.request.urlopen(req) as r:
        return json.load(r)

result = airtable_request("GET", TABLE)
for rec in result["records"]:
    print(rec["id"], rec["fields"])

Filter Records with Formula

import urllib.parse

# Airtable formula syntax (similar to Excel)
formula = "{Status} = 'In Progress'"

params = urllib.parse.urlencode({
    "filterByFormula": formula,
    "fields[]": ["Name", "Status", "Due Date", "Assignee"],  # only these fields
    "sort[0][field]": "Due Date",
    "sort[0][direction]": "asc",
    "maxRecords": 100,
})

url = f"https://api.airtable.com/v0/{BASE_ID}/{urllib.parse.quote(TABLE)}?{params}"
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {TOKEN}"})
with urllib.request.urlopen(req) as r:
    result = json.load(r)

for rec in result["records"]:
    f = rec["fields"]
    print(f"{rec['id']}  {f.get('Name', ''):30s}  {f.get('Status', ''):15s}  {f.get('Due Date', '')}")

Common Formula Examples

# Filter by single-line text
{Name} = 'Alice'

# Filter by checkbox
{Done} = TRUE()

# Filter by date (before today)
IS_BEFORE({Due Date}, TODAY())

# Filter records where a field is not empty
{Assignee} != ''

# Multiple conditions
AND({Status} = 'In Progress', {Priority} = 'High')

# OR condition
OR({Status} = 'To Do', {Status} = 'In Progress')

# Search in text
FIND('keyword', {Description}) > 0

Paginate Through All Records

def list_all_records(table, formula=None, fields=None):
    records = []
    offset = None

    while True:
        params = {"pageSize": 100}
        if formula:
            params["filterByFormula"] = formula
        if fields:
            for i, f in enumerate(fields):
                params[f"fields[{i}]"] = f
        if offset:
            params["offset"] = offset

        query = urllib.parse.urlencode(params)
        url = f"https://api.airtable.com/v0/{BASE_ID}/{urllib.parse.quote(table)}?{query}"
        req = urllib.request.Request(url, headers={"Authorization": f"Bearer {TOKEN}"})
        with urllib.request.urlopen(req) as r:
            data = json.load(r)

        records.extend(data["records"])
        offset = data.get("offset")
        if not offset:
            break

    return records

all_tasks = list_all_records("Tasks", formula="{Status} != 'Done'")
print(f"Total active tasks: {len(all_tasks)}")

Part 3 β€” Creating Records

Create a Single Record

result = airtable_request("POST", TABLE, {
    "records": [
        {
            "fields": {
                "Name": "Set up monitoring dashboard",
                "Status": "To Do",
                "Priority": "High",
                "Due Date": "2026-06-01",
                "Notes": "Use Grafana. See ticket #342.",
            }
        }
    ]
})
created = result["records"][0]
print(f"Created: {created['id']}  {created['fields']['Name']}")

Batch Create Records (up to 10 per request)

items = [
    {"Name": "Write unit tests",       "Status": "To Do", "Priority": "High"},
    {"Name": "Update documentation",   "Status": "To Do", "Priority": "Medium"},
    {"Name": "Deploy to staging",      "Status": "To Do", "Priority": "High"},
    {"Name": "Review pull requests",   "Status": "In Progress", "Priority": "Medium"},
]

# Airtable accepts max 10 records per request
BATCH_SIZE = 10
for i in range(0, len(items), BATCH_SIZE):
    batch = items[i:i + BATCH_SIZE]
    result = airtable_request("POST", TABLE, {
        "records": [{"fields": f} for f in batch]
    })
    for rec in result["records"]:
        print(f"  Created {rec['id']}: {rec['fields']['Name']}")

Part 4 β€” Updating Records

Update a Single Record (PATCH β€” only specified fields)

RECORD_ID = "recXXXXXXXXXXXXXX"

result = airtable_request("PATCH", TABLE, {
    "records": [
        {
            "id": RECORD_ID,
            "fields": {
                "Status": "Done",
                "Completed Date": "2026-05-18",
            }
        }
    ]
})

Batch Update Records (up to 10)

updates = [
    {"id": "recAAAAAAAAAAAAAAAA", "fields": {"Status": "Done"}},
    {"id": "recBBBBBBBBBBBBBBBB", "fields": {"Status": "In Progress", "Assignee": ["recUSER1"]}},
]

for i in range(0, len(updates), 10):
    batch = updates[i:i + 10]
    airtable_request("PATCH", TABLE, {"records": batch})

Upsert Records (create or update based on a key field)

# Available in Airtable API with performUpsert
result = airtable_request("PATCH", TABLE, {
    "performUpsert": {"fieldsToMergeOn": ["Email"]},
    "records": [
        {"fields": {"Email": "[email protected]", "Name": "Alice", "Status": "Active"}},
        {"fields": {"Email": "[email protected]",   "Name": "Bob",   "Status": "Inactive"}},
    ]
})
print(f"Created: {len(result['createdRecords'])}  Updated: {len(result['updatedRecords'])}")

Part 5 β€” Deleting Records

import urllib.parse

# Delete up to 10 records per request
record_ids = ["recAAAAAAAAAAAAAAAA", "recBBBBBBBBBBBBBBBB"]
params = "&".join(f"records[]={rid}" for rid in record_ids)

url = f"https://api.airtable.com/v0/{BASE_ID}/{urllib.parse.quote(TABLE)}?{params}"
req = urllib.request.Request(
    url,
    headers={"Authorization": f"Bearer {TOKEN}"},
    method="DELETE"
)
with urllib.request.urlopen(req) as r:
    result = json.load(r)
print(f"Deleted: {[r['id'] for r in result['records']]}")

Field Type Reference

Airtable typeWrite value format
Single line text"value"
Long text / Notes"multi\nline\ntext"
Number42 or 3.14
Currency99.99
Checkboxtrue or false
Single select"Option Name"
Multiple select["Option A", "Option B"]
Date"2026-05-18"
Date+time"2026-05-18T14:00:00.000Z"
Email"[email protected]"
URL"https://example.com"
Phone"+1 555-000-0000"
Attachment[{"url": "https://..."}]
Linked record["recXXXXXXXXXXXXXX"] (array of record IDs)
User[{"id": "usrXXXXXXXXXXXXXX"}]
Formula / RollupRead-only β€” cannot write
Auto-numberRead-only β€” cannot write

Checklist

  • AIRTABLE_TOKEN set in environment (Personal Access Token, not legacy API key)
  • Token scopes include data.records:read and data.records:write
  • Base ID extracted from URL (appXXXXXXXXXXXXXX)
  • Table name URL-encoded when used in requests
  • Batch operations limited to 10 records per request
  • Pagination implemented for tables with > 100 records (use offset)
  • Formula fields and auto-number fields not written to

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.