
Getting all of your rows out at once
Airtable's API hands back 100 records per request and accepts five requests a second per base. Getting a 50,000 row table out therefore costs 500 requests and a minimum of 100 seconds, before a single retry. The same job in PostgreSQL is one COPY statement. That difference is not about features or polish, and it is not a reason to feel bad about a base that has been earning its keep for three years. It is about who owns the query planner.
A hundred rows at a time
The cap is remarkably consistent across vendors. Airtable's list records endpoint documents pageSize as "less than or equal to 100. Default is 100", and returns an offset token you pass back to get the next page. Notion's API defaults to 100 items with a documented maximum of 100 and a next_cursor to follow. Webflow's List Items endpoint takes a limit parameter with a max of 100. Nobody picked 100 by accident. It is the number that keeps a shared multi tenant backend from being flattened by one customer's export script.
The second cap is rate. Airtable allows 5 requests per second per base, and tells you that a 429 means waiting 30 seconds before anything succeeds again. Webflow ties the limit to the site plan: 60 requests per minute on Starter and Basic, 120 on CMS, eCommerce and Business, with a Retry-After header that typically says 60 seconds. Do the arithmetic on a full Airtable Business base, which holds up to 125,000 records: 1,250 requests at five a second is a little over four minutes of pure crawling with zero failures. A 50,000 item Webflow collection on a Basic site plan is 500 requests at one per second, so more than eight minutes.
Bubble draws the line somewhere harder. Its Data API reference states a limit of 50,000 items in any GET request, and spells out the consequence: if the database holds 100,000 items and the cursor sits at 50,001, you get nothing back. The Enterprise plan raises that to 10,000,000. Below Enterprise, a full export past 50,000 rows means slicing the table with filters and stitching the slices together yourself, and hoping your slices do not overlap or leave a gap.
The crawl is the easy part
Airtable documents a 422 error called LIST_RECORDS_ITERATOR_NOT_AVAILABLE, with this explanation: "Iteration may timeout due to client inactivity or server restarts. It may then restart iteration from the beginning." Read that again if you are mid export at record 43,000. Your one line script is now a job that needs checkpoints, a retry policy, exponential backoff and a dedupe pass at the end. Airtable's own suggestion for heavy read volume is a caching proxy, which is sensible advice and also an admission that you are building infrastructure to read data you already own.
Then there is the thing nobody notices until it bites. Airtable's docs say that if you pass neither sort nor view, "the order of records is arbitrary". Across 500 requests spread over two minutes, while four people on your team are editing records, those pages do not compose into a picture of any single moment. You get a blend of several moments. For a CSV that a human skims, that is fine. For reconciling monthly revenue, or for the dry run before a migration cutover, a blend is worse than useless because it looks correct.
What one statement buys you
PostgreSQL's COPY command writes a table, or the result of any SELECT, straight to a file or to stdout. No page size, no offset token to expire, no 429. Wrap it in a transaction and you get one consistent read of the whole dataset, which is the property the paginated crawl can never give you. The same shape of command loads it back, so your export and your import are the same primitive used in two directions.
This is why export speed shows up as a migration cost rather than a reporting annoyance. On Stay World Class we moved a Webflow and Xano stack to Next.js, NestJS and Supabase, and the data went through a four stage ETL that preserved every legacy ID as a PostgreSQL UUID so old references kept resolving. Each rehearsal of that ETL needed the full dataset pulled fresh. When pulling it takes eight minutes and sometimes restarts from zero, you rehearse less, and rehearsing less is how cutover weekends go wrong. After the move the app also got faster, Lighthouse 55.91 to 91 and LCP down 77%, but the part the team felt first was that a full reload stopped being an afternoon.
When the paginated crawl is the right answer
If your table holds 3,000 rows and you export it once a quarter for an accountant, pagination costs 30 requests and about six seconds, and Airtable's built in CSV download does it with no code at all. Nothing in the numbers above argues for moving. The limits only start to hurt at a specific intersection: large tables, frequent full reads, and a downstream consumer that breaks if two pages disagree.
A simple test: how many rows, how often, and does anything break when the snapshot is inconsistent. Quarterly, small, nothing breaks, then the platform is doing its job and writing an export pipeline would be the expensive choice. We only start making the case for a migration when someone on the team is quietly maintaining a retry loop around an export script, and has been for months.
Sources
- Airtable, "List records" (Web API reference)
- Airtable, "Rate limits" (Web API reference)
- Airtable, "Pricing" (record limits per plan)
- Bubble, "Data API requests" (GET item limits)
- Webflow, "Rate limits" (Data API v2)
- Webflow, "List Items" (Data API v2, limit max 100)
- Notion, "Introduction" (pagination page_size)
- PostgreSQL 18, "COPY"
