Skip to main content

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 in the catalog is a synthetic column labelled SKU.io ID (sku_io_id, a text field 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 resolve records by it, so the value must be unique per record within a dataset. SKU.io derives that identifier per dataset:

Datasetsku_io_id value
ProductsProduct SKU (falls back to the database id if the SKU is blank)
Inventory LevelsComposite product_id:warehouse_id
Sales OrdersSales order number
Purchase OrdersPurchase order number
Sales Order Lines, PO Lines, SuppliersDatabase primary key

Because Inventory Levels has no single natural key, its identifier is the composite product_id:warehouse_id rather than one column. Suppliers key on the numeric database id, so an inbound Airtable key that is not numeric resolves to no record and is ignored rather than matched to the wrong supplier.

The sku_io_id column is never inbound-writable. It is the join, not a payload field — 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 on upsert, which is why the key is always a stable, unique identifier.

Field sources are read as dot paths (for example brand.name or salesOrder.sales_order_number). If a path cannot be resolved for a given record, it yields an empty value rather than an error, so a sync never fails because one relationship is missing.

The outbound record lifecycle

Outbound sync walks a delta — the records that changed since the last run — transforms each one into an Airtable payload, and applies it in batches of up to ten records. 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 sends an upsert that 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 returns the row's record id, SKU.io stores that 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 by record id (key repair)

Once a record is linked to an Airtable record id, later syncs update by that record id, 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

Each pushed record's payload is hashed and the hash is stored with the link. On the next run, if a record's new payload hashes to the same value, 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 — are hard-delete datasets with no archive state and no Archived field, so flag mode cannot mark their removed rows. For those datasets SKU.io coerces 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 that are physically (hard) deleted never do — once the row is gone from the database, there is no delta entry to catch it. A daily reconciliation sweep is the safety net for those. It walks every record 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, a cached tenant-wide gate skips the work without even querying for mappings.
  • When the mapping is disabled or the Airtable connection is disconnected, the queued job returns 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 clients cascades token revocation and webhook-subscription changes on SKU.io's side, and has nothing to do with your Airtable connections.

An Airtable connection is an integration instance, not a registered OAuth client or token. Managing Airtable never touches the OAuth-server side, and vice versa. To make the connection itself, see Connect Airtable.

Last verified: