The ID You Look Up and the Name You Send
The schema call identifies a table's key column by an opaque identifier. The write call wants its human name. Neither is wrong, both are in the same product, and a connector that missed the translation would fail on every table whose columns were ever renamed.
Here is a small awkwardness that appears in a surprising number of APIs and almost never in their documentation. One endpoint answers in identifiers, because identifiers are stable. Another accepts names, because names are what people write. Both choices are defensible. The consequence is that a value read from one cannot be sent to the other, and the code between them has to know.
Two ways to name a column
Every column in an Airtable table has both an opaque identifier that never changes and a human name that anybody can edit.
Ask for a table's structure and the primary column is pointed at by its identifier — correct, because the identifier is what survives a rename. Write a record and say which column to match on, and the field takes a name.
So the connector reads the structure, finds the column whose identifier matches the one declared primary, and takes its name to send to the write.
Two representations of one thing, in one product, on two endpoints. The translation is four lines and it is the whole reason the connector works on a table somebody has tidied.
The three shortcuts that look fine
| Shortcut | Why it is tempting | Where it fails |
|---|---|---|
| Send the identifier to the write | It is what the schema gave you | Refused — the write wants a name |
| Assume the first column is the key | It usually is | A table where somebody reordered |
| Assume a conventional name | Most tables use one | Any table that does not |
The middle row is the interesting one, because it is right often enough to survive testing. The primary column is conventionally first and conventionally called something ordinary — until you meet a base somebody has been living in for two years.
And these failures are not equally visible. Sending an identifier is refused, which is a good failure — somebody sees it immediately. Assuming the wrong column is worse: the write may succeed against a column that is not the key, matching nothing, creating a record every run.
One shortcut fails loudly and one fails by quietly duplicating your data, and it is not obvious from the code which is which.
The pattern behind it
This is the fifth time in this series that a connector has resolved something from the far side rather than assuming it, and putting them together is more useful than any one of them.
-
A column found by identifier, sent by name
Here. The two endpoints disagree about how to refer to a column, so the connector translates.
-
A title column found by its type
On the notes connector, because a title column can be renamed to anything and there is exactly one of that type.
-
A data centre read from the credential
On the audience connector, where the routing arrived inside the key rather than as a setting.
-
An object kind read from an identifier prefix
On the messaging connector, where the value announces what sort of thing it is.
-
A signature role read from the template
On the e-signature connector, where a guessed role produces a document nobody can sign.
The rule underneath all five: anything that has to match something in another system is read from that system. Assumptions about names, positions and conventions are true of the instance you developed against and of nothing else in particular.
Nothing is pasted
The same instinct runs through the setup. The bases your credential can reach are fetched and offered. The tables in the chosen base are fetched and offered. The key column is derived.
At no point does anybody type an identifier, which removes a class of setup error entirely. A mistyped identifier fails with a message about a missing resource, which sends people to check permissions rather than the field they actually got wrong.
And where a chosen table is genuinely not in the base — because it was deleted, or the base changed — the connector says exactly that rather than returning nothing. A miss with a reason is a fixable state; a miss without one is a mystery.
The unglamorous line at the end
Column names that come back empty are dropped from the list offered.
It is trivial and it is the difference between a clean selector and one with a blank entry in it — an entry somebody will eventually select, producing a write against a column with no name, which fails in a way nobody can describe.
Most of the quality in an integration is lines like this. Not clever, not interesting, and each one removing a state somebody would otherwise have to explain over email.
What is in place, layer by layer
How the destination is resolved
The key column resolved from the schema
Found by matching the identifier the table declares as primary, then sent as the name the write endpoint expects.
No assumption about position or naming
A table whose columns have been reordered or renamed resolves correctly, because nothing depends on convention.
Bases offered from your credential
What the token can reach is enumerated and offered by name rather than pasted as an identifier.
Tables offered from the chosen base
Same again, so the whole destination is chosen from real values rather than typed and hoped for.
A missing table named as missing
A chosen table no longer in the base produces that message rather than an empty result somebody has to interpret.
Unusable column names dropped
Empty names are removed from the list offered, so nothing selectable can fail to resolve.
The credential encrypted at rest
Stored on the row for one workspace, never in configuration, and never written into a log line.
The schema names the key column by identifier and the write takes a name. The translation between them is four lines, and without it the connector works only on tables nobody has ever tidied.
Three positions held on purpose
- Anything that has to match something in another system is read from that system. Conventions about names and positions are true of the instance you developed against and of nothing else reliably.
- Identifiers are never typed by a person. A mistyped one fails with a message about a missing resource, which sends whoever is fixing it to permissions rather than to the field they got wrong.
- A miss is reported as a miss with a reason. An empty result and a specific "not in this base" look the same to code and completely different to the person reading it.
Five questions about resolving a destination
How do you know which column is the key?
A good answer sounds like
The table says.
What ours actually is
Read from the schema by identifier, then sent as the name the write expects.
What if we renamed our columns?
A good answer sounds like
It still works.
What ours actually is
Nothing depends on a name or a position being conventional.
Do we paste any identifiers?
A good answer sounds like
No.
What ours actually is
Bases and tables are enumerated and offered by name.
What if the chosen table is deleted?
A good answer sounds like
A clear message.
What ours actually is
Reported as not in the base rather than as an empty result.
Can a blank column appear in the list?
A good answer sounds like
No.
What ours actually is
Empty names are dropped, so nothing selectable can fail to resolve.
Our take
A large part of integration work is noticing that two things which look like the same value are not. An identifier and a name. A currency amount and a subunit count. A message and the bytes of a message. A recipient and a list containing one recipient. Each pair is obvious once stated and invisible while you are writing the line, because both members read correctly in context and nothing in the tooling has anything to say about either. The habit that catches them is small: when you move a value from one call to another, stop and ask what the receiving side actually wants — not what it is called, but what it is.
Ask what the far side actually wants
An identifier and a name look alike in a variable and are not interchangeable across a boundary. Most integration bugs are one of those pairs.
Talk through contact syncingFrequently asked questions
Do we have to tell you which column is the key?
Airtable declares a primary column for every table, and the connector reads it from the schema — resolving the identifier it is given into the name the write expects. Nothing depends on that column being first or being called anything in particular.
What if we rename or reorder our columns?
It keeps working. The key column is resolved from what the table declares rather than from its position or its name, so a base somebody has been reorganising for two years behaves the same as a fresh one.
Do we need to paste a base or table identifier?
No — both are fetched from your credential and offered by name. A mistyped identifier fails with a message about a missing resource, which sends people to check permissions rather than the field they actually got wrong.
What happens if the table we chose is deleted?
The connector reports that it is not in the base, rather than returning nothing. A specific miss is a fixable state; an empty result is something somebody has to work out from the outside.
Why does sending the identifier not work?
Because the write endpoint takes column names rather than identifiers — the same product, two endpoints, two conventions. It is refused rather than silently doing the wrong thing, which is the better of the two failure modes available here.