Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/airtable

README.md

230 lines · 8.5 KB · Markdown

@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:

LaneWhen to useCredential
OAuth (recommended)Multi-account, refresh tokens, shared helpersSaved integration airtable or airtable-<purpose>
Personal access tokenFastest for a single workspace / personal scriptsUser 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
  1. Register an OAuth integration at https://airtable.com/create/oauth
  2. Set the redirect URI exactly to https://kody.codes/connect/oauth
  3. Generate a client secret (Airtable token exchange uses HTTP Basic when a secret exists). Enable PKCE — Airtable requires S256 PKCE on every authorize request.
  4. Under Scopes, enable the scopes this package uses (data.records:read, data.records:write, data.recordComments:read, data.recordComments:write, schema.bases:read).
  5. Connect while signed in to Kody:

https://kody.codes/connect/oauth?provider=airtable&authorizeUrl=https%3A%2F%2Fairtable.com%2Foauth2%2Fv1%2Fauthorize&tokenUrl=https%3A%2F%2Fairtable.com%2Foauth2%2Fv1%2Ftoken&apiBaseUrl=https%3A%2F%2Fapi.airtable.com%2Fv0&scopes=data.records%3Aread%20data.records%3Awrite%20data.recordComments%3Aread%20data.recordComments%3Awrite%20schema.bases%3Aread&flow=confidential&pkce=true&tokenExchangeStyle=basic-form&allowedHosts=api.airtable.com&dashboardUrl=https%3A%2F%2Fairtable.com%2Fcreate%2Foauth

  1. Paste the Airtable client id and client secret into the Kody wizard (never into chat). Approve host api.airtable.com.
  2. 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
  1. Create a token at https://airtable.com/create/tokens
  2. Grant the scopes above and add the bases this agent should see.
  3. Save it (do not paste the value in chat):

https://kody.codes/account/secrets/new?name=airtablePat&description=Airtable%20personal%20access%20token&allowedHosts=api.airtable.com&scope=user

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

ScopeNeeded 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)
  • accountworkairtable-work; airtable-work used as-is; omitted → airtable
  • secretName — PAT secret override
  • auth'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

ExportDescription
.Package overview, connect URLs, export map
./accountsResolve integration/secret names and report what is connected
./smoke-testLocal helper checks plus optional live whoami read (no email)
./viewerAuthenticated user id and scope count (no email)
./list-basesList bases the token can access
./get-baseGet one accessible base by id
./list-tablesList tables in a base (schema)
./get-tableGet one table by id or name
./list-recordsList records (filterByFormula, view, fields, paging)
./get-recordGet one record by id
./create-recordPreview or create a record
./update-recordPreview or update a record
./delete-recordPreview or delete a record
./list-commentsList comments on a record
./create-commentPreview or create a comment
./requestGeneric REST escape hatch
./typesShared 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 ./request when 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