The Airtable REST API turns a base into a programmable database. Anything the UI can do — read, search, filter, create, update, delete, manage attachments — the API does too. Learning the API is the bridge from "Airtable is a tool I click in" to "Airtable is a backend my code can use."
This guide walks through the API from zero. By the end you'll have authenticated, made requests, handled pagination and rate limits, and written code that's safe to run in production. All examples use plain curl for clarity; production code will use a client library (covered at the end).
Step 1: Get a Personal Access Token
Airtable replaced legacy API keys with Personal Access Tokens (PATs) in 2024. PATs are scoped — they specify which bases they can access and what they can do.
- Go to airtable.com/create/tokens.
- Click Create new token.
- Name it (e.g. "My first API token").
- Add scopes:
data.records:read— read records.data.records:write— create, update, delete records.schema.bases:read— read base/table/field metadata.
- Add base access — pick the specific bases the token can access. Avoid all-workspace access unless you really need it.
- Click Create token.
- Copy the token immediately. Airtable shows it once and never again.
For a deeper walkthrough including OAuth alternatives, see our PAT guide.
Step 2: Find Your Base ID
Every Airtable base has a unique ID starting with app. Two ways to find it:
- Open the base in Airtable. The URL looks like
https://airtable.com/appXXXXXXXXXXXXXX/tblYYYYYYYYYYYYYY/.... Theapp...portion is your Base ID. - Go to airtable.com/api, pick the base, and the docs page shows the Base ID prominently.
The same docs page shows every table's ID and field IDs — useful reference material.
Step 3: Make Your First Request
The base URL for the Airtable API is:
https://api.airtable.com/v0/{baseId}/{tableIdOrName}
Substitute your Base ID and table name (URL-encoded if it contains spaces):
curl https://api.airtable.com/v0/appXXXXXXXXXXXXXX/Tasks \
-H "Authorization: Bearer pat_xxxxxxxxxxxxx"
You'll get back JSON like:
{
"records": [
{
"id": "recAAAAAAAAAAAAAA",
"createdTime": "2026-04-15T10:30:00.000Z",
"fields": {
"Name": "Set up onboarding",
"Status": "Done",
"Due Date": "2026-04-20"
}
}
]
}
Each record has an id (the unique record ID, used for updates and links), a createdTime, and fields containing the actual field values.
Step 4: Filtering and Sorting
The API supports several query parameters:
| Parameter | Purpose |
|---|---|
view | Scope results to a specific view |
filterByFormula | Filter with an Airtable formula |
sort[0][field] and sort[0][direction] | Order results |
fields[] | Return only specific fields |
maxRecords | Cap total records returned |
pageSize | Records per page (default 100, max 100) |
Example: get only open high-priority tasks, sorted by due date:
curl -G "https://api.airtable.com/v0/appXXXX/Tasks" \
-H "Authorization: Bearer pat_xxxx" \
--data-urlencode "filterByFormula=AND({Status}='Open', {Priority}='High')" \
--data-urlencode "sort[0][field]=Due Date" \
--data-urlencode "sort[0][direction]=asc"
filterByFormula accepts any Airtable formula that returns true/false per record.
Step 5: Pagination
The API returns up to 100 records per request. When more exist, the response includes an offset cursor:
{
"records": [...],
"offset": "itrXXXX/recXXXX"
}
To get the next page, repeat the request with offset=...:
curl -G "https://api.airtable.com/v0/appXXXX/Tasks" \
-H "Authorization: Bearer pat_xxxx" \
--data-urlencode "offset=itrXXXX/recXXXX"
Loop until the response no longer includes offset. Always paginate — never assume one request returns the whole table.
Step 6: Creating Records
POST to /v0/{baseId}/{tableId} with a JSON body:
curl -X POST https://api.airtable.com/v0/appXXXX/Tasks \
-H "Authorization: Bearer pat_xxxx" \
-H "Content-Type: application/json" \
-d '{
"records": [
{"fields": {"Name": "New task", "Status": "Open", "Priority": "High"}}
]
}'
Up to 10 records per request — batch when creating many.
Common field type formats:
| Field Type | JSON Format |
|---|---|
| Single line / Long text | "text value" |
| Number / Currency / Percent | 123.45 |
| Date | "2026-06-15" |
| Date and time | "2026-06-15T14:00:00.000Z" |
| Single select | "Option Name" |
| Multi-select | ["Option 1", "Option 2"] |
| Linked record | ["recXXXX", "recYYYY"] (array of IDs) |
| Checkbox | true or false |
| Attachments | [{"url": "https://..."}] |
Step 7: Updating Records
Two methods:
- PATCH — update specified fields only (partial update).
- PUT — replace all fields (anything not specified is cleared).
PATCH is what you want 99% of the time:
curl -X PATCH https://api.airtable.com/v0/appXXXX/Tasks \
-H "Authorization: Bearer pat_xxxx" \
-H "Content-Type: application/json" \
-d '{
"records": [
{"id": "recAAAA", "fields": {"Status": "Done"}}
]
}'
Up to 10 records per request, same as create.
Step 8: Deleting Records
DELETE with record IDs as query parameters:
curl -X DELETE "https://api.airtable.com/v0/appXXXX/Tasks?records[]=recAAAA&records[]=recBBBB" \
-H "Authorization: Bearer pat_xxxx"
Up to 10 deletions per request.
Step 9: Upsert (Create or Update)
The API supports upserts via a single POST request:
curl -X POST https://api.airtable.com/v0/appXXXX/Tasks \
-H "Authorization: Bearer pat_xxxx" \
-H "Content-Type: application/json" \
-d '{
"performUpsert": {
"fieldsToMergeOn": ["External ID"]
},
"records": [
{"fields": {"External ID": "ABC123", "Name": "Hello"}}
]
}'
If a record exists with External ID = "ABC123", it's updated. If not, it's created. Use this pattern for sync scripts where you don't want to write your own "search then create or update" logic.
Step 10: Rate Limits and Production Patterns
The hard limits:
- 5 requests per second per base. Exceed and get a 429 with a 30-second cooldown.
- 10 records per request for create/update/delete.
- 100 records per request for list/search.
For batch operations:
import time
for batch in chunks(records, 10):
response = create_records(batch)
time.sleep(0.25) # 4 req/sec safe margin
For high-volume sync, consider:
- Webhooks API for change-data-capture instead of polling.
- Caching — keep a local copy of slowly-changing reference data.
- Enterprise tier — higher rate limits available on request.
Step 11: Use a Client Library
Curl works for learning. For production, use an official or community client library:
| Language | Library |
|---|---|
| JavaScript / TypeScript | airtable (official) |
| Python | pyairtable (community, well-maintained) |
| Go | mehanizm/airtable |
| Ruby | airrecord |
Example with pyairtable:
from pyairtable import Api
api = Api('pat_xxxx')
table = api.table('appXXXX', 'Tasks')
# List, with auto-pagination
records = table.all(formula="{Status}='Open'")
# Create
table.create({'Name': 'New task', 'Status': 'Open'})
# Update
table.update('recAAAA', {'Status': 'Done'})
# Upsert
table.batch_upsert(records, key_fields=['External ID'])
For Python specifically, see our Airtable with Python guide.
Common Mistakes
Mistake 1: Hardcoding PATs in code. Use environment variables. Commit a token to GitHub and it ends up in attack scripts within hours.
Mistake 2: Single requests for batch operations. 100 individual creates take 100 requests + 25 seconds of rate-limit delay. One batched call per 10 records is 90% faster.
Mistake 3: Not handling 429 errors. Production code must catch 429 and back off. Sleep 30 seconds, retry, escalate if it persists.
Mistake 4: Filtering client-side. Pulling 50,000 records to filter them in code wastes bandwidth and burns rate limit. Use filterByFormula server-side.
Mistake 5: Using table names instead of IDs. Names break if anyone renames the table. Use the tbl... ID for stable references.
Troubleshooting
401 Unauthorized. PAT is wrong, expired, or doesn't have access to the base. Confirm via the Airtable API docs page for the base.
403 Forbidden. PAT is valid but lacks the required scope. Add data.records:write if you're getting this on a POST/PATCH.
404 Not Found. Base ID or table ID/name is wrong. Double-check both.
422 Unprocessable Entity. Field values don't match the schema. Most often: passing a string where a number is expected, or an unknown single-select option.
429 Too Many Requests. Rate limited. Back off 30 seconds, then retry. Reduce request frequency or batch more aggressively.
Next Steps
The API is the foundation for any serious Airtable integration. Once you're comfortable with CRUD operations and pagination, the natural next steps are: building a sync script between Airtable and another system, integrating Airtable into your product backend, and writing scripts that automate data cleanup and reporting tasks.
For deeper dives, see our finding Airtable IDs guide, Python guide, scripting guide, and webhooks guide. For production integrations that need to be reliable at scale, get in touch.