Airtable API Integration
Connect your Airtable bases to Glyph via API and generate professional PDFs from your data programmatically.
What You Can Build
Section titled “What You Can Build”With Glyph + Airtable, you can automatically generate:
- Invoices from your billing table
- Quotes & Proposals from your sales pipeline
- Reports from project tracking data
- Contracts from your client database
- Certificates from your records
- Packing slips from inventory data
Quick Start
Section titled “Quick Start”-
Get your Airtable Personal Access Token
Go to airtable.com/create/tokens, click Create new token, name it “Glyph Integration”, and add these scopes:
data.records:read— Read records from tablesschema.bases:read— Read base and table schemas
Under Access, select the bases you want to connect. Click Create token and copy the value (starts with
pat). -
Connect your base
Terminal window curl -X POST https://api.glyph.you/v1/airtable/connect \-H "Authorization: Bearer gk_your_glyph_api_key" \-H "Content-Type: application/json" \-d '{"apiKey": "patXXXXXXXXXXXXXX"}'This returns a list of accessible bases with their IDs.
-
Pick a table and inspect its schema
Terminal window # List tables in your basecurl https://api.glyph.you/v1/airtable/bases/appXXXXXX/tables \-H "Authorization: Bearer gk_your_glyph_api_key" \-H "X-Airtable-Key: patXXXXXXXXXXXXXX"# Get field schema for a specific tablecurl https://api.glyph.you/v1/airtable/bases/appXXXXXX/tables/tblXXXXXX/schema \-H "Authorization: Bearer gk_your_glyph_api_key" \-H "X-Airtable-Key: patXXXXXXXXXXXXXX" -
Fetch a record and generate a PDF
Terminal window # Get a single recordcurl https://api.glyph.you/v1/airtable/bases/appXXXXXX/tables/tblXXXXXX/records/recXXXXXX \-H "Authorization: Bearer gk_your_glyph_api_key" \-H "X-Airtable-Key: patXXXXXXXXXXXXXX"# Create a preview session with that datacurl -X POST https://api.glyph.you/v1/preview \-H "Authorization: Bearer gk_your_glyph_api_key" \-H "Content-Type: application/json" \-d '{"template": "quote-modern", "data": { ... }}'# Generate the PDFcurl -X POST https://api.glyph.you/v1/generate \-H "Authorization: Bearer gk_your_glyph_api_key" \-H "Content-Type: application/json" \-d '{"sessionId": "SESSION_ID", "format": "pdf"}' \--output invoice.pdf
API Endpoints Reference
Section titled “API Endpoints Reference”All Airtable endpoints require two credentials:
- Glyph API Key —
Authorization: Bearer gk_your_api_key - Airtable Token — either in the request body (
POST /connect) or via theX-Airtable-Keyheader (all other endpoints)
POST /v1/airtable/connect
Section titled “POST /v1/airtable/connect”Validate an Airtable token and list all accessible bases.
Request body:
{ "apiKey": "patXXXXXXXXXXXXXX"}The apiKey field must start with pat (Personal Access Token) or key (legacy API key).
Response (200):
{ "success": true, "bases": [ { "id": "appXXXXXXXXXXXXXX", "name": "Sales Pipeline", "permissionLevel": "create" } ], "message": "Connected successfully. Found 1 accessible base(s)."}Errors:
| Code | Status | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | Missing or empty apiKey |
INVALID_KEY_FORMAT | 400 | Key does not start with pat or key |
AIRTABLE_AUTH_ERROR | 401 | Token is invalid, expired, or revoked |
CONNECT_ERROR | 500 | Unexpected server error |
GET /v1/airtable/bases/:baseId/tables
Section titled “GET /v1/airtable/bases/:baseId/tables”List all tables in a base with field and view counts.
Response (200):
{ "baseId": "appXXXXXXXXXXXXXX", "tables": [ { "id": "tblXXXXXXXXXXXXXX", "name": "Invoices", "description": "Customer invoices", "primaryFieldId": "fldXXXXXXXXXXXXXX", "fieldCount": 12, "viewCount": 3 } ]}Errors:
| Code | Status | Meaning |
|---|---|---|
MISSING_AIRTABLE_KEY | 400 | X-Airtable-Key header not provided |
AIRTABLE_ERROR | 400 | Invalid API key or base ID |
GET /v1/airtable/bases/:baseId/tables/:tableId/schema
Section titled “GET /v1/airtable/bases/:baseId/tables/:tableId/schema”Get detailed field schema for a specific table. The tableId parameter accepts either a table ID (tblXXX) or the table name (case-sensitive).
Response (200):
{ "baseId": "appXXXXXXXXXXXXXX", "table": { "id": "tblXXXXXXXXXXXXXX", "name": "Invoices", "description": "Customer invoices", "primaryFieldId": "fldXXXXXXXXXXXXXX" }, "fields": [ { "id": "fldXXXXXXXXXXXXXX", "name": "Invoice Number", "type": "autoNumber", "description": null }, { "id": "fldYYYYYYYYYYYYYY", "name": "Amount", "type": "currency", "options": { "precision": 2, "symbol": "$" } } ], "views": [ { "id": "viwXXXXXXXXXXXXXX", "name": "All Invoices", "type": "grid" } ], "aiSchema": { "tableName": "Invoices", "tableDescription": "Customer invoices", "fields": [ { "name": "Invoice Number", "type": "autoNumber", "glyphType": "number", "description": "Invoice Number field", "mustachePath": "fields.Invoice Number", "isArray": false, "hasConditional": true } ] }}The aiSchema object is designed for AI agents and template engines. It provides:
glyphType— the normalized type Glyph uses for renderingmustachePath— the Mustache template path to reference this fieldisArray— whether the field holds multiple valueshasConditional— alwaystruesince any Airtable field can be empty
Errors:
| Code | Status | Meaning |
|---|---|---|
MISSING_AIRTABLE_KEY | 400 | Header not provided |
TABLE_NOT_FOUND | 404 | Table ID/name does not exist in the base |
GET /v1/airtable/bases/:baseId/tables/:tableId/records
Section titled “GET /v1/airtable/bases/:baseId/tables/:tableId/records”Fetch records from a table. Returns both formatted (template-ready) and raw records.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
maxRecords | number | 5 | Number of records to return (1—100) |
view | string | — | Filter by Airtable view name |
Response (200):
{ "baseId": "appXXXXXXXXXXXXXX", "tableId": "tblXXXXXXXXXXXXXX", "tableName": "Invoices", "recordCount": 2, "records": [ { "_id": "recXXXXXXXXXXXXXX", "_createdTime": "2024-01-15T10:30:00.000Z", "fields": { "Invoice Number": "1001", "Client Name": "Acme Corp", "Amount": "2,500.00", "Due Date": "2/15/2024", "Paid": true } } ], "rawRecords": [ { "id": "recXXXXXXXXXXXXXX", "createdTime": "2024-01-15T10:30:00.000Z", "fields": { "Invoice Number": 1001, "Client Name": "Acme Corp", "Amount": 2500, "Due Date": "2024-02-15", "Paid": true } } ]}records contains formatted values (localized numbers, dates, currency symbols). rawRecords contains the original Airtable values.
Errors:
| Code | Status | Meaning |
|---|---|---|
MISSING_AIRTABLE_KEY | 400 | Header not provided |
TABLE_NOT_FOUND | 404 | Table does not exist |
RECORDS_ERROR | 400 | Failed to fetch records (bad view name, permissions, etc.) |
GET /v1/airtable/bases/:baseId/tables/:tableId/records/:recordId
Section titled “GET /v1/airtable/bases/:baseId/tables/:tableId/records/:recordId”Fetch a single record by its Airtable record ID.
Response (200):
{ "baseId": "appXXXXXXXXXXXXXX", "tableId": "tblXXXXXXXXXXXXXX", "tableName": "Invoices", "record": { "_id": "recXXXXXXXXXXXXXX", "_createdTime": "2024-01-15T10:30:00.000Z", "fields": { "Invoice Number": "1001", "Client Name": "Acme Corp", "Amount": "2,500.00" } }, "rawRecord": { "id": "recXXXXXXXXXXXXXX", "createdTime": "2024-01-15T10:30:00.000Z", "fields": { "Invoice Number": 1001, "Client Name": "Acme Corp", "Amount": 2500 } }}Errors:
| Code | Status | Meaning |
|---|---|---|
MISSING_AIRTABLE_KEY | 400 | Header not provided |
TABLE_NOT_FOUND | 404 | Table does not exist |
RECORD_ERROR | 400 | Record not found or inaccessible |
Field Type Mapping
Section titled “Field Type Mapping”Glyph normalizes Airtable field types for optimal PDF rendering. The full mapping:
| Airtable Type | Glyph Type | Formatting Applied |
|---|---|---|
singleLineText, multilineText, barcode, aiText | text | None (pass-through) |
richText | html | Preserved as markdown |
email | Pass-through | |
url | url | Pass-through |
phoneNumber | phone | Pass-through |
number, autoNumber, count, rating | number | Locale-formatted, max 2 decimals |
currency | currency | Locale-formatted, 2 decimal places, currency symbol |
percent | percent | Locale-formatted |
date | date | toLocaleDateString("en-US") |
dateTime, createdTime, lastModifiedTime | datetime | toLocaleString("en-US") |
duration | duration | Pass-through |
checkbox | boolean | Converted to true/false |
singleSelect | select | Single string value |
multipleSelects | multiselect | Array of strings |
attachment | attachment | Array of objects; use fields.FieldName.0.url for first image |
multipleRecordLinks | links | Array of record IDs |
collaborator, createdBy, lastModifiedBy | user | Pass-through |
multipleCollaborators | users | Array |
formula | computed | Pass-through |
rollup | computed | Pass-through |
lookup | lookup | Pass-through |
button | button | Pass-through |
MCP Server Integration
Section titled “MCP Server Integration”AI agents using the Glyph MCP server can work with Airtable data through three dedicated tools.
glyph_create_source
Section titled “glyph_create_source”Register an Airtable table as a data source for PDF generation.
{ "type": "airtable", "name": "Invoices Table", "config": { "apiKey": "patXXXXXXXXXXXXXX", "baseId": "appXXXXXXXXXXXXXX", "tableName": "Invoices" }}Returns a sourceId you use in subsequent calls.
glyph_suggest_mappings
Section titled “glyph_suggest_mappings”Get AI-powered mapping suggestions between a template’s placeholders (e.g., {{client.name}}) and the source’s field names (e.g., “Customer Name”). Returns a list of suggested mappings with confidence scores.
{ "templateId": "tpl_XXXXX", "sourceId": "src_XXXXX"}glyph_generate_from_source
Section titled “glyph_generate_from_source”Generate one or more PDFs using a saved template and a connected data source. Supports single-record generation via recordId or batch generation with filters.
{ "templateId": "tpl_XXXXX", "sourceId": "src_XXXXX", "recordId": "recXXXXXXXXXXXXXX"}Typical Agent Workflow
Section titled “Typical Agent Workflow”glyph_create_source— connect the Airtable tableglyph_suggest_mappings— let AI map fields to template placeholdersglyph_link_template— save the mappingglyph_generate_from_source— generate PDFs on demand
Automation and Scripting
Section titled “Automation and Scripting”Using with Airtable’s Omni AI
Section titled “Using with Airtable’s Omni AI”Airtable’s Omni AI can interact with the Glyph API to generate PDFs from your data. Here are example prompts you can use:
Generate an invoice:
Generate a PDF invoice for the selected record using Glyph API.Use the invoice-clean template and map fields from this table.Create a quote document:
Use the Glyph API to create a professional quote PDF from this record.Include the client name, line items, and totals.Batch generate receipts:
For each record in the "Completed Orders" view, generate a receipt PDFusing Glyph's /v1/create endpoint and save the download link to the"Receipt URL" field.Airtable Scripting Extension (Quick Start)
Section titled “Airtable Scripting Extension (Quick Start)”The simplest way to generate a PDF from a record using the one-shot /v1/create endpoint:
// Airtable Scripting Extension - Generate PDF with Glyphconst table = base.getTable('Invoices');const record = await input.recordAsync('Select a record', table);
const response = await fetch('https://api.glyph.you/v1/create', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ data: { invoice: { number: record.getCellValue('Invoice Number') }, client: { name: record.getCellValue('Client Name') }, items: record.getCellValue('Line Items') || [], total: record.getCellValue('Total') } })});
const result = await response.json();output.markdown(`[Download PDF](${result.url})`);Airtable Scripting Extension (Full Control)
Section titled “Airtable Scripting Extension (Full Control)”For more control over templates and the generation process, use this inside an Airtable Scripting block:
// Airtable Scripting Extensionconst GLYPH_API_KEY = "gk_your_api_key";const AIRTABLE_TOKEN = "patXXXXXXXXXXXXXX";const BASE_ID = "appXXXXXXXXXXXXXX";const TABLE_ID = "tblXXXXXXXXXXXXXX";const API = "https://api.glyph.you";
// 1. Get the current recordconst table = base.getTable("Invoices");const record = await input.recordAsync("Pick a record", table);
// 2. Fetch formatted data via Glyphconst dataResp = await fetch( `${API}/v1/airtable/bases/${BASE_ID}/tables/${TABLE_ID}/records/${record.id}`, { headers: { "Authorization": `Bearer ${GLYPH_API_KEY}`, "X-Airtable-Key": AIRTABLE_TOKEN, }, });const { record: formattedRecord } = await dataResp.json();
// 3. Create previewconst previewResp = await fetch(`${API}/v1/preview`, { method: "POST", headers: { "Authorization": `Bearer ${GLYPH_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ template: "quote-modern", data: { client: { name: formattedRecord.fields["Client Name"], email: formattedRecord.fields["Email"], }, meta: { quoteNumber: formattedRecord.fields["Invoice Number"], date: formattedRecord.fields["Date"], }, }, }),});const { sessionId } = await previewResp.json();
// 4. Generate PDFconst pdfResp = await fetch(`${API}/v1/generate`, { method: "POST", headers: { "Authorization": `Bearer ${GLYPH_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ sessionId, format: "pdf" }),});
output.markdown(`PDF generated! Session: ${sessionId}`);Airtable Automations (Webhook)
Section titled “Airtable Automations (Webhook)”Trigger PDF generation when a record enters a view:
- In Airtable, create an Automation with trigger “When record enters view”
- Add a “Run a script” action
- Use the script above, replacing
input.recordAsync()with the trigger’s record ID - Optionally add a “Send email” action to deliver the PDF
Airtable Automations - Email with PDF Attachment
Section titled “Airtable Automations - Email with PDF Attachment”Getting a generated PDF to appear as an email attachment in Airtable requires a specific two-automation pattern. This is because Airtable’s email action can only attach files from attachment fields, not from script outputs.
Why a single automation fails:
- Script
output.set()creates text values, not attachment-compatible sources - Using “Find records” between script and email doesn’t help
- The email action’s Attachment field only recognizes actual attachment field values
The Two-Automation Pattern:
-
Automation 1: Generate PDF and Save URL
- Trigger: When record is created (or enters a specific view)
- Action: Run a script that generates the PDF and writes the URL to an attachment field
-
Automation 2: Send Email with Attachment
- Trigger: When “Generated PDF” field is not empty
- Action: Send email with the attachment field included
This works because the second automation only fires after the attachment field is properly populated by the first automation.
Setting Up the Attachment Field:
In your Airtable table, create a field called “Generated PDF” with type Attachment. This field will store the PDF URL that Airtable converts into a downloadable attachment.
Automation 1: Complete Script
Use this script in your first automation’s “Run a script” action:
// Airtable Automation Script - Generate PDF with Glyph// This uses /v1/create which returns a hosted URL (required for attachments)
const config = input.config();const recordId = config.recordId;
const GLYPH_API_KEY = "gk_your_api_key";const API = "https://api.glyph.you";
// Get the table and recordconst table = base.getTable("Invoices");const record = await table.selectRecordAsync(recordId);
if (!record) { throw new Error(`Record ${recordId} not found`);}
// Build the data payload from your record fieldsconst data = { invoice: { number: record.getCellValue("Invoice Number"), date: record.getCellValue("Date"), }, client: { name: record.getCellValue("Client Name"), email: record.getCellValue("Client Email"), address: record.getCellValue("Address"), }, items: record.getCellValue("Line Items") || [], total: record.getCellValue("Total"),};
// Call Glyph /v1/create - this returns a hosted PDF URLconst response = await fetch(`${API}/v1/create`, { method: "POST", headers: { "Authorization": `Bearer ${GLYPH_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ data }),});
if (!response.ok) { const error = await response.text(); throw new Error(`Glyph API error: ${error}`);}
const result = await response.json();const pdfUrl = result.url;
// IMPORTANT: Update the attachment field with the correct format// Airtable attachment fields require an array of objects with 'url' propertyawait table.updateRecordAsync(recordId, { "Generated PDF": [{ url: pdfUrl }]});
console.log(`PDF generated and saved: ${pdfUrl}`);Automation 1 Setup:
- Create a new automation
- Set trigger to “When record is created” (or “When record matches conditions”)
- Add input configuration: Add a field called
recordIdand map it to the triggering record’s ID - Add “Run a script” action with the script above
- Replace
"gk_your_api_key"with your actual Glyph API key - Adjust field names to match your table schema
Automation 2 Setup:
- Create a second automation
- Set trigger to “When record matches conditions”
- Set condition: Generated PDF → is not empty
- Add “Send email” action
- Configure recipient, subject, and body
- In the Attachments field, select your “Generated PDF” field
Common Gotchas:
Troubleshooting:
| Issue | Solution |
|---|---|
| ”Field ‘Generated PDF’ cannot accept the provided value” | Ensure you’re passing [{ url: pdfUrl }] (array format) |
| Email sends but has no attachment | Check that Automation 2 trigger fires after the field is populated |
| Script timeout | Large PDFs may take longer; consider increasing timeout or using simpler templates |
| ”Record not found” | Verify recordId is correctly mapped in automation input config |
Example: Invoice from Airtable (curl)
Section titled “Example: Invoice from Airtable (curl)”A complete end-to-end example using only curl:
# VariablesGLYPH_KEY="gk_your_api_key"AT_KEY="patXXXXXXXXXXXXXX"BASE="appXXXXXXXXXXXXXX"TABLE="tblXXXXXXXXXXXXXX"RECORD="recXXXXXXXXXXXXXX"API="https://api.glyph.you"
# Step 1: Fetch the recordRECORD_DATA=$(curl -s "$API/v1/airtable/bases/$BASE/tables/$TABLE/records/$RECORD" \ -H "Authorization: Bearer $GLYPH_KEY" \ -H "X-Airtable-Key: $AT_KEY")
echo "$RECORD_DATA" | jq '.record.fields'
# Step 2: Create a preview session# Map Airtable fields to your template's expected data shapeSESSION_ID=$(curl -s -X POST "$API/v1/preview" \ -H "Authorization: Bearer $GLYPH_KEY" \ -H "Content-Type: application/json" \ -d '{ "template": "quote-modern", "data": { "client": { "name": "Acme Corp", "email": "billing@acme.com" }, "lineItems": [ {"description": "Consulting", "quantity": 10, "rate": 250} ], "totals": { "subtotal": "$2,500.00", "tax": "$200.00", "total": "$2,700.00" }, "meta": { "quoteNumber": "INV-1001", "date": "1/27/2026", "validUntil": "2/27/2026" } } }' | jq -r '.sessionId')
echo "Session: $SESSION_ID"
# Step 3: (Optional) Modify with AIcurl -s -X POST "$API/v1/modify" \ -H "Authorization: Bearer $GLYPH_KEY" \ -H "Content-Type: application/json" \ -d "{ \"sessionId\": \"$SESSION_ID\", \"prompt\": \"Add a professional header with company logo placeholder\" }" | jq '.selfCheckPassed'
# Step 4: Generate PDFcurl -s -X POST "$API/v1/generate" \ -H "Authorization: Bearer $GLYPH_KEY" \ -H "Content-Type: application/json" \ -d "{\"sessionId\": \"$SESSION_ID\", \"format\": \"pdf\"}" \ --output invoice.pdf
echo "Saved to invoice.pdf"Connecting via the Landing Page
Section titled “Connecting via the Landing Page”The easiest way to get started without writing code:
- Visit glyph.you
- Click Connect Airtable in the navigation or hero section
- Paste your Personal Access Token
- Select your Base from the dropdown
- Select the Table containing your data
- Choose a Template Style that fits your document type
- Preview your generated PDF with sample data
- Download or refine with AI modifications
Troubleshooting
Section titled “Troubleshooting””Invalid Airtable API key format”
Section titled “”Invalid Airtable API key format””Your token must start with pat (Personal Access Token) or key (legacy API key). Create a new token at airtable.com/create/tokens.
”Failed to connect to Airtable”
Section titled “”Failed to connect to Airtable””Check that:
- Your token has the required scopes (
data.records:read,schema.bases:read) - The token has access to the base you are trying to connect
- The token has not expired or been revoked
”Table not found”
Section titled “”Table not found””The tableId parameter accepts both table IDs (starting with tbl) and table names (case-sensitive). Verify the exact value using the list tables endpoint.
Empty or Missing Fields
Section titled “Empty or Missing Fields”- Records come back with
nullfor any field that has no value - Verify the view name if using the
viewquery parameter - Check that your token has read access to the table
”Airtable API key required”
Section titled “”Airtable API key required””All endpoints except POST /connect require the X-Airtable-Key header. Make sure you are sending it as a separate header, not in the request body.
Next Steps
Section titled “Next Steps”- POST /v1/preview — Generate document previews
- POST /v1/modify — AI-powered modifications
- POST /v1/generate — PDF generation
- Templates Overview — Available templates
- MCP Server — AI agent integration