Skip to main content
RapidDev - Software Development Agency
replit-integrationsStandard API Integration

How to Integrate Replit with Airtable

To integrate Replit with Airtable, generate a Personal Access Token from airtable.com/create/tokens, store it in Replit Secrets (lock icon in sidebar), and call the Airtable REST API to read, create, update, and delete records. Airtable is one of the simplest database integrations available for Replit — no SQL knowledge, SSL configuration, or IP allowlisting required.

What you'll learn

  • How to generate an Airtable Personal Access Token with the right scopes
  • How to store your Airtable credentials securely in Replit Secrets
  • How to read, create, update, and delete Airtable records from your backend
  • How to filter and sort Airtable records using formula queries
  • How to build a simple API server that uses Airtable as a database
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner16 min read10 minutesDatabaseMarch 2026RapidDev Engineering Team
TL;DR

To integrate Replit with Airtable, generate a Personal Access Token from airtable.com/create/tokens, store it in Replit Secrets (lock icon in sidebar), and call the Airtable REST API to read, create, update, and delete records. Airtable is one of the simplest database integrations available for Replit — no SQL knowledge, SSL configuration, or IP allowlisting required.

Why Use Airtable as a Database for Replit Apps?

Airtable bridges the gap between spreadsheets and databases. Your non-technical team members can manage data visually in Airtable's familiar grid interface, while your Replit code reads and writes that data through a clean REST API. This makes Airtable particularly valuable for apps where the data is maintained by people who are not developers — content teams, operations managers, or clients who need to update records without touching a database.

Compared to other database integrations, Airtable is the simplest to get started with on Replit. There is no connection string, no SSL configuration, no IP allowlisting (Airtable accepts connections from any IP), and no schema to design in code — you just build your table in the Airtable UI and immediately have a working API. The trade-off is that Airtable is optimized for human-friendly data management rather than high-performance queries: it is not suitable for apps with thousands of records per second, complex joins, or strict transactional requirements.

Common Airtable and Replit patterns include: CMS backends where a content team updates records in Airtable and a Replit web app displays them, form-to-Airtable pipelines where submissions are added as records, internal tools where operations teams manage lists that power automated workflows, and prototypes that need a real database without the setup overhead of PostgreSQL or MySQL.

Integration method

Standard API Integration

The Replit-Airtable integration works by authenticating with an Airtable Personal Access Token stored in Replit Secrets, then calling the Airtable REST API to read and write records in your Airtable bases. Every table in Airtable automatically has a REST API endpoint — no database setup, no schema migrations, and no connection strings required. This makes Airtable one of the most beginner-friendly database integrations available for Replit projects.

Prerequisites

  • An Airtable account (free tier works) with at least one base and table created
  • A Replit account with a Node.js or Python Repl ready
  • Basic familiarity with REST APIs (no SQL knowledge required)

Step-by-step guide

1

Generate an Airtable Personal Access Token

Airtable uses Personal Access Tokens (PATs) for API authentication — these replaced the older API key model in 2023. PATs are more secure because you can scope them to specific bases and limit what operations they can perform. To generate a token, log into Airtable and go to airtable.com/create/tokens. Click 'Create new token'. Give it a descriptive name like 'Replit Integration'. Under 'Scopes', select the permissions your integration needs: - data.records:read — to read records from tables - data.records:write — to create, update, and delete records - schema.bases:read — optional, to list fields and table structure Under 'Access', click 'Add a base' and select the Airtable base(s) your Replit app will access. Limiting access to specific bases is a security best practice — if the token is ever compromised, it can only affect those bases. Click 'Create token'. Airtable shows the token once — copy it immediately. You will not be able to see it again (you can only regenerate it). You will also need your Base ID, which appears in the Airtable API documentation for your base. Open your base in Airtable, click 'Help' in the top right, then 'API documentation'. The URL changes to airtable.com/developers/web/api/introduction, and the left panel shows your Base ID — it starts with 'app' followed by a random string (e.g., appXXXXXXXXXXXXXX). Copy this as well.

Pro tip: You can also find your Base ID in the Airtable API docs by going to airtable.com/api, selecting your base, and looking at the URL — it will include the Base ID in the format airtable.com/appXXXX/api/docs. Each table in the base also has a Table ID starting with 'tbl'.

Expected result: You have an Airtable Personal Access Token (starts with 'pat') and a Base ID (starts with 'app') ready to store in Replit.

2

Store Airtable Credentials in Replit Secrets

Click the lock icon (🔒) in the Replit sidebar to open the Secrets panel. Add the following secrets: 1. AIRTABLE_TOKEN — your Personal Access Token (starts with 'pat') 2. AIRTABLE_BASE_ID — your Base ID (starts with 'app') Optionally add: 3. AIRTABLE_TABLE_NAME — the name of your primary table (e.g., 'Blog Posts' or 'Contacts') Click 'Add Secret' after each entry. These values are encrypted with AES-256 and stored separately from your code. They are never visible in your file tree or version control. For apps that use multiple tables, either store them all as secrets (AIRTABLE_TABLE_POSTS, AIRTABLE_TABLE_USERS) or define them as constants in your configuration file — table names are not sensitive, but keeping them as secrets makes it easier to change them without code changes. Access in code: - Node.js: process.env.AIRTABLE_TOKEN - Python: os.environ['AIRTABLE_TOKEN'] The Airtable API base URL follows this pattern: https://api.airtable.com/v0/{BASE_ID}/{TABLE_NAME} So your request URL will be: https://api.airtable.com/v0/{process.env.AIRTABLE_BASE_ID}/{tableName}

Pro tip: After adding the secret, verify it loaded correctly by adding a quick test: console.log('Token loaded:', !!process.env.AIRTABLE_TOKEN) — this prints true/false without revealing the actual value.

Expected result: AIRTABLE_TOKEN and AIRTABLE_BASE_ID appear in the Replit Secrets panel with values hidden.

3

Read and Write Records with Node.js

The Airtable REST API is one of the cleanest APIs available — each endpoint is just your Base ID and table name as URL components, with bearer token authentication. You can use the official airtable npm package or plain HTTP requests with fetch. The code below uses the built-in fetch API (Node 18+, Replit's default) with no additional packages required. It covers the four core operations: listing records, creating a record, updating a record, and deleting a record. It also shows how to filter records using Airtable's formula syntax. For simple projects, this is all the Airtable code you need — no driver setup, no connection pools, no SSL certificates.

airtable.js
1// airtable.js — Airtable REST API client (no external packages needed)
2const BASE_URL = 'https://api.airtable.com/v0';
3
4function getHeaders() {
5 const token = process.env.AIRTABLE_TOKEN;
6 if (!token) throw new Error('AIRTABLE_TOKEN secret not set');
7 return {
8 'Authorization': `Bearer ${token}`,
9 'Content-Type': 'application/json'
10 };
11}
12
13function tableUrl(tableName) {
14 const baseId = process.env.AIRTABLE_BASE_ID;
15 if (!baseId) throw new Error('AIRTABLE_BASE_ID secret not set');
16 return `${BASE_URL}/${baseId}/${encodeURIComponent(tableName)}`;
17}
18
19// List records (up to 100 per request by default)
20async function listRecords(tableName, options = {}) {
21 const params = new URLSearchParams();
22
23 // Filter using Airtable formula syntax
24 if (options.filterFormula) {
25 params.set('filterByFormula', options.filterFormula);
26 }
27 // Sort records
28 if (options.sortField) {
29 params.set('sort[0][field]', options.sortField);
30 params.set('sort[0][direction]', options.sortDirection || 'asc');
31 }
32 // Limit results
33 if (options.maxRecords) {
34 params.set('maxRecords', options.maxRecords);
35 }
36 // Select specific fields only
37 if (options.fields && options.fields.length > 0) {
38 options.fields.forEach((field, i) => {
39 params.set(`fields[${i}]`, field);
40 });
41 }
42
43 const url = `${tableUrl(tableName)}?${params.toString()}`;
44 const response = await fetch(url, { headers: getHeaders() });
45 if (!response.ok) {
46 throw new Error(`Airtable error ${response.status}: ${await response.text()}`);
47 }
48 const data = await response.json();
49 return data.records; // Array of { id, createdTime, fields: {...} }
50}
51
52// Create a new record
53async function createRecord(tableName, fields) {
54 const response = await fetch(tableUrl(tableName), {
55 method: 'POST',
56 headers: getHeaders(),
57 body: JSON.stringify({ fields })
58 });
59 if (!response.ok) {
60 throw new Error(`Airtable error ${response.status}: ${await response.text()}`);
61 }
62 return response.json(); // Returns { id, createdTime, fields }
63}
64
65// Update a record (partial update — only specified fields)
66async function updateRecord(tableName, recordId, fields) {
67 const response = await fetch(`${tableUrl(tableName)}/${recordId}`, {
68 method: 'PATCH',
69 headers: getHeaders(),
70 body: JSON.stringify({ fields })
71 });
72 if (!response.ok) {
73 throw new Error(`Airtable error ${response.status}: ${await response.text()}`);
74 }
75 return response.json();
76}
77
78// Delete a record
79async function deleteRecord(tableName, recordId) {
80 const response = await fetch(`${tableUrl(tableName)}/${recordId}`, {
81 method: 'DELETE',
82 headers: getHeaders()
83 });
84 if (!response.ok) {
85 throw new Error(`Airtable error ${response.status}: ${await response.text()}`);
86 }
87 return response.json(); // Returns { deleted: true, id: '...' }
88}
89
90module.exports = { listRecords, createRecord, updateRecord, deleteRecord };

Pro tip: Airtable field names in the API are the exact same names as the column headers in your table. If your column is called 'First Name', use 'First Name' (with the space) in your fields object. Field names are case-sensitive.

Expected result: listRecords() returns an array of record objects, each with an id and fields containing your Airtable column values.

4

Build an Express API Server Using Airtable

With the Airtable client module ready, build an Express server that exposes clean API endpoints. This server demonstrates reading records with filtering, creating records, and updating records — the three most common operations for web applications using Airtable as a backend. Install Express: npm install express The example below assumes a 'Products' table with Name, Price, Category, and In Stock (checkbox) columns. Adapt the field names to match your actual Airtable table.

server.js
1// server.js — Express server with Airtable backend
2const express = require('express');
3const {
4 listRecords, createRecord, updateRecord, deleteRecord
5} = require('./airtable');
6
7const app = express();
8app.use(express.json());
9
10const TABLE = process.env.AIRTABLE_TABLE_NAME || 'Products';
11
12// GET /products — list all products, optional ?category= filter
13app.get('/products', async (req, res) => {
14 try {
15 const options = {
16 sortField: 'Name',
17 sortDirection: 'asc'
18 };
19
20 // Filter by category if provided
21 if (req.query.category) {
22 options.filterFormula = `{Category} = "${req.query.category}"`;
23 }
24
25 // Filter to only in-stock items
26 if (req.query.inStockOnly === 'true') {
27 options.filterFormula = options.filterFormula
28 ? `AND(${options.filterFormula}, {In Stock} = TRUE())`
29 : '{In Stock} = TRUE()';
30 }
31
32 const records = await listRecords(TABLE, options);
33
34 // Flatten the Airtable response format
35 const products = records.map(r => ({
36 id: r.id,
37 name: r.fields['Name'] || '',
38 price: r.fields['Price'] || 0,
39 category: r.fields['Category'] || '',
40 inStock: r.fields['In Stock'] || false
41 }));
42
43 res.json({ products, count: products.length });
44 } catch (err) {
45 console.error(err.message);
46 res.status(500).json({ error: err.message });
47 }
48});
49
50// POST /products — create a new product
51app.post('/products', async (req, res) => {
52 try {
53 const { name, price, category } = req.body;
54 if (!name || price === undefined) {
55 return res.status(400).json({ error: 'name and price are required' });
56 }
57
58 const record = await createRecord(TABLE, {
59 'Name': name,
60 'Price': parseFloat(price),
61 'Category': category || '',
62 'In Stock': true
63 });
64
65 res.status(201).json({ id: record.id, fields: record.fields });
66 } catch (err) {
67 res.status(500).json({ error: err.message });
68 }
69});
70
71// PATCH /products/:id — update a product
72app.patch('/products/:id', async (req, res) => {
73 try {
74 const updates = {};
75 if (req.body.name !== undefined) updates['Name'] = req.body.name;
76 if (req.body.price !== undefined) updates['Price'] = parseFloat(req.body.price);
77 if (req.body.inStock !== undefined) updates['In Stock'] = req.body.inStock;
78
79 const record = await updateRecord(TABLE, req.params.id, updates);
80 res.json({ id: record.id, fields: record.fields });
81 } catch (err) {
82 res.status(500).json({ error: err.message });
83 }
84});
85
86// DELETE /products/:id
87app.delete('/products/:id', async (req, res) => {
88 try {
89 const result = await deleteRecord(TABLE, req.params.id);
90 res.json(result);
91 } catch (err) {
92 res.status(500).json({ error: err.message });
93 }
94});
95
96app.listen(3000, '0.0.0.0', () => {
97 console.log(`Airtable API server running — table: ${TABLE}`);
98});

Pro tip: Airtable returns records in batches of up to 100. If your table has more than 100 records, the API response includes an 'offset' field. To fetch all records, make additional requests with &offset={value} until no offset is returned in the response.

Expected result: GET /products returns a JSON array of product records from your Airtable table. POST /products creates a new row visible immediately in the Airtable UI.

5

Python Implementation and Deployment

For Python projects, the same Airtable API calls work with the requests library. Install it in the Replit Shell: pip install requests flask The Python implementation below mirrors the Node.js version with a Flask server. Both implementations use only the Airtable REST API over HTTPS — no special drivers, connection strings, or SSL certificates needed. For deployment, click Deploy in the top-right corner of Replit and choose 'Autoscale' for web APIs. Airtable's API is rate-limited to 5 requests per second per base, so Autoscale's ability to scale up handles traffic spikes while Airtable's rate limits are usually not a concern for typical web app traffic patterns.

app.py
1# app.py Python Flask server with Airtable backend
2import os
3import requests
4from flask import Flask, request, jsonify
5from urllib.parse import quote
6
7app = Flask(__name__)
8
9AIRTABLE_BASE = 'https://api.airtable.com/v0'
10TABLE_NAME = os.environ.get('AIRTABLE_TABLE_NAME', 'Contacts')
11
12def get_headers():
13 token = os.environ.get('AIRTABLE_TOKEN')
14 if not token:
15 raise ValueError('AIRTABLE_TOKEN secret not set')
16 return {
17 'Authorization': f'Bearer {token}',
18 'Content-Type': 'application/json'
19 }
20
21def table_url(table_name=None):
22 base_id = os.environ.get('AIRTABLE_BASE_ID')
23 if not base_id:
24 raise ValueError('AIRTABLE_BASE_ID secret not set')
25 name = table_name or TABLE_NAME
26 return f'{AIRTABLE_BASE}/{base_id}/{quote(name)}'
27
28@app.route('/records', methods=['GET'])
29def list_records():
30 """List records with optional filter formula."""
31 params = {}
32 filter_formula = request.args.get('filter')
33 if filter_formula:
34 params['filterByFormula'] = filter_formula
35
36 sort_field = request.args.get('sort')
37 if sort_field:
38 params['sort[0][field]'] = sort_field
39 params['sort[0][direction]'] = request.args.get('direction', 'asc')
40
41 try:
42 response = requests.get(
43 table_url(),
44 headers=get_headers(),
45 params=params
46 )
47 response.raise_for_status()
48 data = response.json()
49 # Flatten to simple list
50 records = [
51 {'id': r['id'], **r['fields']}
52 for r in data.get('records', [])
53 ]
54 return jsonify({'records': records, 'count': len(records)})
55 except Exception as e:
56 return jsonify({'error': str(e)}), 500
57
58@app.route('/records', methods=['POST'])
59def create_record():
60 """Create a new record from JSON body."""
61 fields = request.json
62 if not fields:
63 return jsonify({'error': 'Request body required'}), 400
64 try:
65 response = requests.post(
66 table_url(),
67 headers=get_headers(),
68 json={'fields': fields}
69 )
70 response.raise_for_status()
71 record = response.json()
72 return jsonify({'id': record['id'], 'fields': record['fields']}), 201
73 except Exception as e:
74 return jsonify({'error': str(e)}), 500
75
76@app.route('/records/<record_id>', methods=['PATCH'])
77def update_record(record_id):
78 """Update specific fields of a record."""
79 fields = request.json
80 try:
81 response = requests.patch(
82 f'{table_url()}/{record_id}',
83 headers=get_headers(),
84 json={'fields': fields}
85 )
86 response.raise_for_status()
87 return jsonify(response.json())
88 except Exception as e:
89 return jsonify({'error': str(e)}), 500
90
91if __name__ == '__main__':
92 app.run(host='0.0.0.0', port=3000)

Pro tip: For Autoscale deployments, add a .replit file that sets deploymentTarget = 'cloudrun' and the run command. Airtable's 5 requests/second rate limit per base means your app naturally handles moderate traffic well — cache frequently-read records in memory for a few seconds to reduce API calls further.

Expected result: GET /records returns records from your Airtable table. POST /records creates a new record that appears in Airtable within seconds.

Common use cases

Content Management System Backend

Use an Airtable base as a lightweight CMS where your content team adds, edits, and organizes content visually, while your Replit web app reads and displays it. Blog posts, team member profiles, product listings, and event schedules are all good candidates.

Replit Prompt

Build a Node.js Express API server that reads records from an Airtable table called 'Blog Posts'. Each record has Title, Body, Author, PublishDate, and Status fields. Create a GET /posts endpoint that returns only published posts (Status = 'Published') sorted by PublishDate descending. Store AIRTABLE_TOKEN and AIRTABLE_BASE_ID in environment variables.

Copy this prompt to try it in Replit

Form Submissions to Airtable

Build a contact form or lead capture form on your Replit app that writes submissions directly to Airtable as new records. Your sales or support team immediately sees new submissions in Airtable without needing to check a database or email.

Replit Prompt

Create a Flask server with a /submit POST endpoint. When called with name, email, company, and message fields, create a new record in an Airtable 'Contact Form' table with those fields plus a Submitted Date field set to the current timestamp. Return a success response with the new record ID.

Copy this prompt to try it in Replit

Internal Operations Tool

Build an internal dashboard that your operations team uses to manage inventory, tasks, or customer orders stored in Airtable. The dashboard reads and writes to Airtable records in real time, with your Replit backend handling business logic that Airtable automations cannot perform.

Replit Prompt

Create an Express API with endpoints to list inventory items from Airtable (GET /inventory), update item stock quantity (PATCH /inventory/:id), and mark items as reordered (POST /inventory/:id/reorder). Read the Airtable base ID and table name from environment variables.

Copy this prompt to try it in Replit

Troubleshooting

Error: 401 Unauthorized — 'AUTHENTICATION_REQUIRED' or 'invalid token'

Cause: The Personal Access Token was not provided correctly, has been revoked, or the token's scopes do not include the operation being attempted (e.g., trying to write records with a read-only token).

Solution: Verify the token in Replit Secrets is correct by checking it starts with 'pat'. Go to airtable.com/create/tokens and confirm the token is still active. Ensure the token's scopes include data.records:write for write operations. Re-generate the token if needed and update the AIRTABLE_TOKEN secret.

typescript
1// Quick token validation
2const response = await fetch('https://api.airtable.com/v0/meta/whoami', {
3 headers: { 'Authorization': `Bearer ${process.env.AIRTABLE_TOKEN}` }
4});
5console.log(response.status, await response.json());

Error: 404 Not Found — table or base not found

Cause: The Base ID or table name in the URL does not match your Airtable base. Table names are case-sensitive and must match exactly. Spaces in table names must be URL-encoded.

Solution: Verify the AIRTABLE_BASE_ID starts with 'app' and matches the ID shown in your Airtable base's API documentation. Check that the table name matches exactly — 'Blog Posts' and 'blog posts' are different. Use encodeURIComponent() or quote() to handle spaces in table names automatically.

typescript
1// Always URL-encode table names with spaces
2const url = `https://api.airtable.com/v0/${process.env.AIRTABLE_BASE_ID}/${encodeURIComponent('My Table Name')}`;

Error: 422 Unprocessable Entity on record creation

Cause: The fields object contains a field name that does not exist in the table, a value type that does not match the column type (e.g., passing a string to a number field), or a required field is missing.

Solution: Check the error response body — it includes a specific message about which field is invalid. Verify field names exactly match your Airtable column headers (case-sensitive). For number fields, ensure values are numbers not strings. For checkbox fields, use true/false not 'Yes'/'No'. For select fields, use a value that exists in the field's options.

typescript
1// Correct field types for Airtable
2const fields = {
3 'Name': 'string value', // Text field
4 'Price': 29.99, // Number field — use number, not string
5 'In Stock': true, // Checkbox — use boolean
6 'Status': 'Active', // Single select — must match option exactly
7 'Tags': ['tech', 'web'] // Multi-select — use array of strings
8};

Only 100 records returned even though the table has more

Cause: The Airtable API returns a maximum of 100 records per request by default. For tables with more than 100 records, the response includes an 'offset' field for pagination.

Solution: Implement pagination by checking for the 'offset' field in the response and making additional requests with ?offset={value} until no offset is returned. For most use cases, adding &maxRecords=100 and using filters (?filterByFormula=...) to retrieve only the records you need is more efficient than fetching all records.

typescript
1// Fetch all records with pagination
2async function listAllRecords(tableName) {
3 const allRecords = [];
4 let offset = null;
5 do {
6 const params = offset ? `?offset=${offset}` : '';
7 const url = `${tableUrl(tableName)}${params}`;
8 const response = await fetch(url, { headers: getHeaders() });
9 const data = await response.json();
10 allRecords.push(...data.records);
11 offset = data.offset; // undefined when no more pages
12 } while (offset);
13 return allRecords;
14}

Best practices

  • Store your AIRTABLE_TOKEN and AIRTABLE_BASE_ID in Replit Secrets (lock icon 🔒) rather than hardcoding them — these values give write access to your base and should never appear in code files or version control.
  • Create a Personal Access Token with the minimum required scopes — if your integration only reads data, use data.records:read only. This limits the damage if the token is ever exposed.
  • Always URL-encode table names when building API URLs — tables with spaces or special characters in their names (e.g., 'Blog Posts', 'Q4 Goals') will cause 404 errors if not encoded with encodeURIComponent() (JavaScript) or urllib.parse.quote() (Python).
  • Use Airtable's filterByFormula parameter to fetch only the records you need rather than fetching all records and filtering in code — this reduces API calls and response payload size significantly for large tables.
  • Implement pagination for tables with more than 100 records by checking for the 'offset' field in API responses and making follow-up requests — the default 100-record limit will silently truncate your data otherwise.
  • Cache frequently-read data in memory for short periods (30-60 seconds) to stay within Airtable's rate limit of 5 requests per second per base — for read-heavy dashboards, this is the single most important performance optimization.
  • Deploy to Autoscale for Airtable-backed web apps — Airtable's API is stateless and requires no persistent connection, making it well-suited for Autoscale deployments that scale to zero during idle periods.

Alternatives

Frequently asked questions

How do I connect Replit to Airtable?

Generate a Personal Access Token at airtable.com/create/tokens with data.records:read and data.records:write scopes. Store it as AIRTABLE_TOKEN in Replit Secrets (lock icon in sidebar). Find your Base ID in your Airtable base's API documentation and store it as AIRTABLE_BASE_ID. Then call the Airtable REST API from your Node.js or Python backend using the Base ID and table name in the URL.

Does Airtable work well with Replit?

Yes — Airtable is one of the easiest database integrations on Replit because it requires no SSL configuration, no IP allowlisting (Airtable accepts connections from any IP, including Replit's dynamic IPs), and no database drivers. You only need an HTTP client (built into Node 18+ and available via requests in Python) and a Personal Access Token.

How do I find my Airtable Base ID?

Open your Airtable base and click 'Help' in the top-right corner, then 'API documentation'. The API documentation page URL contains your Base ID (it starts with 'app'). Alternatively, go to airtable.com/api, select your base, and the Base ID appears in all the example API URLs shown on the left panel.

Can I use Airtable with Replit for free?

Yes. Airtable's free plan allows up to 1,000 records per base, 5 editors, and 1 GB of attachments — more than enough for development and small production apps. The Airtable API is available on all plans. Replit's free tier works for development. For production, deploy on Replit's paid plans for guaranteed uptime.

Why is Airtable returning only 100 records?

The Airtable API returns a maximum of 100 records per request. If your table has more, the response includes an 'offset' field — make another request with ?offset={value} appended to fetch the next batch, and repeat until no offset is returned. For most cases, use filterByFormula to retrieve only the records you need rather than paginating through all of them.

What is the difference between Airtable's API key and Personal Access Token?

Airtable deprecated account-level API keys in 2023 and replaced them with Personal Access Tokens (PATs). PATs are more secure because you can scope them to specific bases and specific operations (read-only vs read-write). Always use PATs for new integrations — the old API key approach is no longer recommended and may stop working in the future.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.