← back to Ventura Claw
server/connectors/airtable.js
54 lines
// Airtable — REAL. Use Personal Access Token (PAT).
const API = "https://api.airtable.com/v0";
function token(c) {
const t = c?.AIRTABLE_PAT || process.env.AIRTABLE_PAT;
if (!t) throw new Error("AIRTABLE_PAT not set");
return t;
}
async function call(method, path, body, c) {
const r = await fetch(`${API}${path}`, {
method,
headers: { "Authorization": `Bearer ${token(c)}`, "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined
});
const d = r.status === 204 ? {} : await r.json();
if (!r.ok) throw new Error(`airtable ${path} ${r.status}: ${d.error?.message || JSON.stringify(d).slice(0,200)}`);
return d;
}
module.exports = {
meta: { id: "airtable", name: "Airtable", category: "data", docsUrl: "https://airtable.com/developers/web/api", auth: "oauth2", realImpl: true },
fields: [
{ key: "AIRTABLE_PAT", label: "Personal Access Token", type: "password", required: true, hint: "pat... — airtable.com/create/tokens" },
{ key: "AIRTABLE_BASE_ID", label: "Default Base ID", type: "text", required: false, hint: "appXXXX... (optional default; can override per call)" }
],
configured(c) { return !!(c?.AIRTABLE_PAT || process.env.AIRTABLE_PAT); },
async health(c) {
if (!this.configured(c)) return { ok: false, reason: "no_token" };
try { const d = await call("GET", "/meta/whoami", null, c); return { ok: true, user_id: d.id, email: d.email }; }
catch (e) { return { ok: false, reason: e.message }; }
},
actions: {
async "bases.list"(_, c) { return call("GET", "/meta/bases", null, c); },
async "tables.list"({ base_id }, c) {
const id = base_id || process.env.AIRTABLE_BASE_ID || c?.AIRTABLE_BASE_ID;
if (!id) throw new Error("base_id required");
return call("GET", `/meta/bases/${id}/tables`, null, c);
},
async "records.list"({ base_id, table, max }, c) {
const id = base_id || process.env.AIRTABLE_BASE_ID || c?.AIRTABLE_BASE_ID;
if (!id || !table) throw new Error("base_id and table required");
return call("GET", `/${id}/${encodeURIComponent(table)}?maxRecords=${max||25}`, null, c);
},
async "record.create"({ base_id, table, fields }, c) {
const id = base_id || process.env.AIRTABLE_BASE_ID || c?.AIRTABLE_BASE_ID;
if (!id || !table) throw new Error("base_id and table required");
return call("POST", `/${id}/${encodeURIComponent(table)}`, { fields }, c);
},
async "record.update"({ base_id, table, record_id, fields }, c) {
const id = base_id || process.env.AIRTABLE_BASE_ID || c?.AIRTABLE_BASE_ID;
if (!id || !table || !record_id) throw new Error("base_id, table, record_id required");
return call("PATCH", `/${id}/${encodeURIComponent(table)}/${record_id}`, { fields }, c);
}
}
};