Read and update Airtable bases, tables, records, and comments through OAuth or a PAT.
- Other
- airtable
- bases
- tables
- records
- comments
- oauth
- pat
- License
- MIT
- Published
- August 22, 2026
- Pinned commit
c117e1e- Rating
- No ratings yet
- Forks
- 0
- Stars
- 0
- Adaptation effort
- —
README
@kody/airtable
Intent
Provide reusable, account-agnostic Airtable helpers so Kody agents can read and
update bases, tables, records, and comments through a saved airtable /
airtable-* OAuth integration or a personal access token — without
hand-rolling REST. Mutations are previewable with dryRun: true and only
run live after confirm: true.
This listing is meant to be forked. After you fork, connect your Airtable
workspace. Do not treat the live @kody/airtable package storage as yours.
Share this listing as https://kody.codes/@kody/airtable
When To Use
- List or inspect bases, tables, records, and comments
- Create, update, or delete records after explicit confirmation
- Create comments after explicit confirmation
- Call an unwrapped Airtable REST path through
./request - Connect more than one Airtable account via
integrationName/account
Auth
Airtable has no built-in Kody OAuth app. Choose one lane:
| Lane | When to use | Credential |
|---|---|---|
| OAuth (recommended) | Multi-account, refresh tokens, shared helpers | Saved integration airtable or airtable-<purpose> |
| Personal access token | Fastest for a single workspace / personal scripts | User secret airtablePat (or airtablePat-<purpose>) |
Both lanes send Authorization: Bearer …. Required API host:
api.airtable.com. Approve it in the account secrets UI.
Lane A — BYO OAuth
- Register an OAuth integration at https://airtable.com/create/oauth
- Set the redirect URI exactly to
https://kody.codes/connect/oauth - Generate a client secret (Airtable token exchange uses HTTP Basic when a secret exists). Enable PKCE — Airtable requires S256 PKCE on every authorize request.
- Under Scopes, enable the scopes this package uses
(
data.records:read,data.records:write,data.recordComments:read,data.recordComments:write,schema.bases:read). - Connect while signed in to Kody:
- Paste the Airtable client id and client secret into the Kody wizard (never
into chat). Approve host
api.airtable.com. - Reconnect later with https://kody.codes/connect/oauth?provider=airtable
OAuth authorize URL: https://airtable.com/oauth2/v1/authorize. Token URL:
https://airtable.com/oauth2/v1/token. Flow: confidential (client secret) plus
S256 PKCE. Token exchange style: basic-form. Airtable scopes are
space-separated.
To connect a second workspace, change provider (for example
provider=airtable-work) and pass integrationName: 'airtable-work' on every
call.
Lane B — Personal access token
- Create a token at https://airtable.com/create/tokens
- Grant the scopes above and add the bases this agent should see.
- Save it (do not paste the value in chat):
For a second workspace/token, use a distinct secret name such as
airtablePat-work and pass secretName: 'airtablePat-work' (or
account: 'work', which resolves to airtable-work / airtablePat-work).
Scopes
| Scope | Needed for |
|---|---|
schema.bases:read | ./list-bases, ./get-base, ./list-tables, ./get-table |
data.records:read | ./list-records, ./get-record |
data.records:write | ./create-record, ./update-record, ./delete-record |
data.recordComments:read | ./list-comments |
data.recordComments:write | ./create-comment |
./viewer and ./smoke-test call /meta/whoami, which does not require a
scope. This package does not request user.email:read.
If Airtable returns 401/403 or an insufficient-scope error, helpers throw a message that names the missing scope and the next setup URL (reconnect OAuth with that scope, or save a PAT).
Multiple accounts
Every export accepts:
integrationName/integration— exact saved OAuth name (airtable-work)account—work→airtable-work;airtable-workused as-is; omitted →airtablesecretName— PAT secret overrideauth—'oauth'or'pat'when both exist
Do not hard-code a personal base id, table name, or alias.
Safety
Mutating helpers require confirm: true. Pass dryRun: true to inspect the
REST payload without calling Airtable. ./request treats GET as read-only;
POST / PUT / PATCH / DELETE need confirmation.
No package-owned jobs are enabled.
Exports
| Export | Description |
|---|---|
. | Package overview, connect URLs, export map |
./accounts | Resolve integration/secret names and report what is connected |
./smoke-test | Local helper checks plus optional live whoami read (no email) |
./viewer | Authenticated user id and scope count (no email) |
./list-bases | List bases the token can access |
./get-base | Get one accessible base by id |
./list-tables | List tables in a base (schema) |
./get-table | Get one table by id or name |
./list-records | List records (filterByFormula, view, fields, paging) |
./get-record | Get one record by id |
./create-record | Preview or create a record |
./update-record | Preview or update a record |
./delete-record | Preview or delete a record |
./list-comments | List comments on a record |
./create-comment | Preview or create a comment |
./request | Generic REST escape hatch |
./types | Shared TypeScript types |
Smoke test
import smokeTest from 'kody:@kody/airtable/smoke-test'
export default async function main() {
return await smokeTest()
}Without credentials this returns { ok: true, live: false } plus the connect
and PAT URLs. After OAuth or a PAT is saved it reads /meta/whoami and returns
{ live: true, hasViewerId } without email.
Preview a mutation without credentials:
import createRecord from 'kody:@kody/airtable/create-record'
export default async function main() {
return await createRecord({
baseId: 'appXXXXXXXXXXXXXX',
tableIdOrName: 'tblXXXXXXXXXXXXXX',
fields: { Name: 'Follow up on onboarding' },
dryRun: true,
})
}Examples
import listBases from 'kody:@kody/airtable/list-bases'
import listTables from 'kody:@kody/airtable/list-tables'
export default async function main() {
const { items: bases } = await listBases()
return await listTables({
baseId: bases[0]?.id ?? '',
})
}import createRecord from 'kody:@kody/airtable/create-record'
export default async function main() {
const preview = await createRecord({
baseId: 'appXXXXXXXXXXXXXX',
tableIdOrName: 'tblXXXXXXXXXXXXXX',
fields: { Name: 'Investigate checkout timeout' },
dryRun: true,
})
// After the user confirms the exact base, table, and fields:
return await createRecord({
baseId: 'appXXXXXXXXXXXXXX',
tableIdOrName: 'tblXXXXXXXXXXXXXX',
fields: { Name: 'Investigate checkout timeout' },
confirm: true,
})
}Unwrapped REST:
import request from 'kody:@kody/airtable/request'
export default async function main() {
return await request({
path: '/meta/whoami',
})
}Notes
- REST base:
https://api.airtable.com/v0 - Prefer table ids (
tbl…) over names when a name can collide or contain spaces. - Helpers project slim objects (no collaborator emails). Use
./requestwhen you need extra fields. - This package is not affiliated with, endorsed by, or sponsored by Formagrid Inc dba Airtable.
Branding
The community icon is Airtable's official product mark as published at https://airtable.com/images/favicon/mstile-144x144.png (the current isometric logomark). Pixels are unmodified. Airtable® and the Airtable trademark are registered trademarks of Formagrid Inc dba Airtable.
Docs
Report this listing
Log in to report this listing.