How Airtable sync works
The Airtable integration keeps a copy of your SKU.io data in your own Airtable base and keeps it current without any middleware. This page explains how the sync engine decides what to write, how it avoids duplicate rows, and how deletions and edits flow in each direction.
Two sync directions
Every mapping you build has one direction, and the direction sets who owns the data.
- Outbound pushes SKU.io to Airtable. SKU.io is the source of truth, and each sync writes your records into the mapped Airtable table. All seven datasets support outbound.
- Inbound pulls a narrow whitelist of fields from Airtable back into SKU.io. Only Products, Sales Orders, and Suppliers support inbound, and each writes only a small set of safe fields — never status, dates, inventory, costs, or pricing. See Inbound safety and the field whitelist for the exact whitelist per dataset.
A single mapping never mixes directions. You can point both an outbound and an inbound mapping at the same Airtable table, and SKU.io validates that they do not fight over the same fields.
The merge key: SKU.io ID
Every dataset's first field is a column labelled SKU.io ID, marked as the key. This is the join between a SKU.io record and its Airtable row — it is the single most important field in the whole engine.
Outbound upserts merge on it, and inbound lookups find records by it, so the value has to be unique per record within a dataset. SKU.io derives that identifier per dataset:
| Dataset | SKU.io ID value |
|---|---|
| Products | Product SKU (falls back to the product's SKU.io ID if the SKU is blank) |
| Inventory Levels | The product and warehouse combined |
| Sales Orders | Sales order number |
| Purchase Orders | Purchase order number |
| Sales Order Lines, PO Lines, Suppliers | The record's SKU.io ID |
Because Inventory Levels has no single natural key, its identifier combines the product and the warehouse rather than using one column. Suppliers key on a numeric ID, so an inbound Airtable key that isn't numeric matches nothing and is ignored rather than attached to the wrong supplier.
The SKU.io ID column is never writable from Airtable. It's the join, not data — SKU.io reads it to find the record but never lets Airtable change it. Two SKU.io records that produced the same key value would collide and overwrite each other, which is why the key is always a stable, unique identifier.
A mapped field that reaches through a relationship — a product's brand name, or the order number on an order line — comes back empty when that relationship is missing, rather than failing. A sync never breaks because one record lacks a related record.
The outbound record lifecycle
Outbound sync walks a delta — the records that changed since the last run — turns each one into an Airtable row, and writes them in batches of up to ten. What it does with each record depends on whether SKU.io has seen that record in Airtable before.
New records upsert on the key
When SKU.io has never linked a record to an Airtable row, it merges on the SKU.io ID key field. Airtable finds any existing row with a matching key and updates it, or creates a fresh row if none matches. This is why re-running a sync never spawns duplicate rows — even if the local link was lost, the key still matches the existing Airtable row. Once Airtable hands back the row, SKU.io remembers the link for next time.
If a mapping has no key field configured, the upsert has nothing to merge on and always creates a fresh row, so the key field should always be present.
Linked records update the row they already own (key repair)
Once a record is linked to an Airtable row, later syncs update that row directly, not by the key. This is what makes key changes safe. If you rename a product's SKU or a sales order number — the very value stored in the key field — the next sync writes the new key onto the same row it already owns, repairing the key in place instead of creating a duplicate that merges on the old value. Only never-before-linked records use the key-merge path.
Unchanged records are skipped
SKU.io remembers a fingerprint of what it last wrote for each record. On the next run, if the record would produce exactly the same row, SKU.io counts it as skipped and writes nothing. This keeps repeated syncs cheap and keeps your Airtable API usage down, since only genuinely changed records are written.
How deletions and archives propagate
When you archive or delete a record in SKU.io, the mapping's delete mode decides what happens to its Airtable row:
- Flag (the default) ticks the Archived checkbox on the Airtable row and keeps the row. This is non-destructive — your Airtable data stays put, marked as archived.
- Delete removes the Airtable row and drops the local link.
- Ignore leaves the Airtable row untouched and makes no Airtable call at all.
This applies both to archived records caught in the normal sync delta and to real-time single-record deletes. Under delete mode, if an archived record was never linked to an Airtable row in the first place, there is nothing to remove, so it is skipped and counted.
Flag mode depends on the Archived field being mapped. Some datasets — Inventory Levels and the order and PO line datasets — have no archive state and no Archived field, so flag mode has nothing to tick on their removed rows. For those datasets SKU.io switches flag mode to Delete when you save the mapping, so a removed row never lingers active in Airtable forever.
The daily reconciliation sweep
Archived records show up in the normal sync delta, but records deleted outright never do — once a record is gone from SKU.io, there's nothing left to notice. A daily reconciliation sweep is the safety net for those. It walks every remembered link, finds the ones whose SKU.io record no longer exists, and applies the mapping's delete mode to the orphaned Airtable rows.
The sweep handles a few situations carefully:
- Flag mode with an Archived field mapped — the orphaned rows are flagged as archived.
- Flag mode with no Archived field mapped — the rows cannot be flagged, so instead of silently doing nothing, the sweep counts them as unflaggable and logs a one-time warning that surfaces the gap.
- Delete mode — orphaned rows are deleted, and the local link is dropped only when the Airtable delete actually succeeds. A transient Airtable failure keeps the link so the row is retried on the next run rather than being orphaned.
Mappings with delete mode set to ignore are excluded from the sweep entirely.
Real-time pushes
Beyond the scheduled sync, a mapping can push edits to Airtable within seconds of a change. When you turn on the Real-time sync toggle, SKU.io watches the mapped records for creates, updates, and deletes and queues a single push per change.
The real-time path is debounced and deduplicated so a burst of edits to one record collapses into a single write: the push is delayed about ten seconds to let rapid edits settle, and duplicate pushes for the same record inside a fifteen-second window are collapsed into one. It is best-effort by design — if a real-time push fails it is logged and counted, and the scheduled sync (plus the daily sweep) still covers the change.
Real-time pushes are deliberately cheap to skip when they are not wanted:
- During a bulk import or data repair, real-time pushes are suppressed so a large import does not fan out thousands of individual writes. Those records are picked up by the next scheduled sync instead.
- When a dataset has no real-time mapping, the work is skipped outright — SKU.io doesn't even go looking for one.
- When the mapping is disabled or the Airtable connection is disconnected, the queued push exits without writing.
- The monthly Airtable API cap is respected on the real-time path too, not only on scheduled runs.
For the full set of rate limits, the monthly API cap, retry behaviour, and sync-health alerts, see Reliability, limits, and sync-health alerts.
SKU.io's OAuth server vs. the Airtable OAuth client
SKU.io has two unrelated OAuth surfaces, and it helps to keep them apart when you think about what an Airtable connection is:
- The Airtable connection — SKU.io is the client. When you connect Airtable, you authorize SKU.io outbound into your Airtable workspace, and the result is a per-tenant Airtable connection. This is the surface the whole sync engine runs on.
- SKU.io's own OAuth server / Personal Access Tokens — SKU.io is the server. Admins register third-party apps that authorize inbound, against SKU.io. Revoking one of those apps also revokes its tokens and its webhook subscriptions on SKU.io's side, and has nothing to do with your Airtable connections.
An Airtable connection is neither a registered OAuth app nor a token. Managing Airtable never touches the OAuth-server side, and vice versa. To make the connection itself, see Connect Airtable.
Related reading
- Sync your data to Airtable — build a mapping and run your first sync.
- Review changes from Airtable — approve or reject inbound edits.
- Inbound safety and the field whitelist — exactly which fields Airtable can write back.
- Dataset and field reference — every dataset, field, and cell type.
- Sync-mapping settings reference — delete mode, real-time toggle, and other mapping options.