Contacts and attributes
How records arriving from several data sources become one audience contact, what the merging attributes decide, and how to read the project's live attribute catalogue: system versus custom attributes, data types, uniqueness and event-derived fields.
Everything you filter, count or message operates on the audience contact: one record per person, per project. It is not the record any single system sent you. It is what the project built by deciding that several incoming records described the same human being, and then reconciling them. Getting that distinction right up front removes most of the surprises an integration hits later.
From datasource contact to audience contact
Each data source keeps its own records exactly as they arrived: those are datasource contacts. The project then merges them into the audience by the merging attributes configured on it.
A record arrives
Your CRM sends a person with
_user_id: "12345"and an email. The e-commerce source sends a person with the same_user_idand a phone number.The project looks for a match
Merging is driven by the attributes marked unique on the project.
_user_idis the primary one; a project typically configures extras such as_emailand_phone_mobile. A match on any of them triggers a merge.The values are reconciled
When two sources disagree about the same attribute, the project's priority rules decide the winner. The Ingest data source takes precedence by default.
The timelines join
Every event from every merged record now hangs off the one audience contact, so a person has a single timeline regardless of how many systems reported parts of it.
The result keeps a trail back to its origins: _datasources lists the sources the contact was built from, and _ds_contact_ids the underlying datasource contact ids. Use them to trace a contact back, not to reason about a specific value.
What this means in practice
- Counts of "contacts from source X" are counts of contacts that source helped build, and the same person can be counted under two sources. The numbers are not a partition of the audience.
- A value can change without your integration doing anything, because another source with higher priority sent a different one.
- Do not model identity yourself. If you need two records to be the same person, give them the same
_user_id(or a matching unique attribute) and let the merge happen. Writing your own dedupe on top produces a second, divergent identity model.
The attribute catalogue
The attributes a contact can carry are project-specific: they depend on how the project is configured and what its data sources feed it. There is no static list to memorise, because yours is not the same as anyone else's. Read it live.
Every attribute enabled on contacts in this project, with its type and flags.
curl "$BASE/specs/attributes" \
-H "Authorization: Bearer $INSTASENT_TOKEN"Each entry carries the fields you need to decide whether you can filter on it, write to it, or show it:
uidstringThe identifier you use as key in a filter condition, and as the field name when writing contacts. System attributes start with an underscore (_user_id, _email, _phone_mobile); custom ones never do.
labelstringThe human label, ready to display. Prefer it over displayLabel, which is the raw stored value and may still carry the panel's translation markers.
dataTypestringWhat the attribute holds: string, number, boolean, date and friends. It decides which operators are valid on it, and whether a probe can list its values at all.
uniquebooleanWhether the attribute takes part in merging. The full set is also returned as metadata.uniqueAttributes, which is the fastest way to learn a project's identity model in one call.
custombooleanWhether it was created by the account rather than shipped by the platform.
readonlybooleanWhether it can be written. Computed and platform-owned attributes are read-only.
eventBasedbooleanWhether the value is derived from the contact's events rather than supplied by a source. These update on their own as events arrive.
multivalueintegerMaximum number of values the attribute accepts. 1 means single-valued. On a multivalue attribute a contact can hold several values at once, which changes how you read any distribution over it.
mappeablebooleanWhether a data source can map an incoming field onto it.
enabledbooleanWhether the attribute is in use in this project. The endpoint returns enabled attributes.
System, custom and internal
- System attributes are shipped by the platform and always begin with
_. They cover identity (_user_id,_email,_phone_mobile), derived geography and language, subscription state, and platform bookkeeping. - Custom attributes are created by the account for whatever the business needs. Their uid cannot start with an underscore, which is the rule that keeps the two namespaces from colliding.
- Internal attributes exist for the platform's own use and are not part of your working surface.
Existing is not the same as usable
The catalogue tells you an attribute exists. It says nothing about whether it has data behind it. An attribute declared on the project and filled on 2% of contacts is present in the specs and absent in practice, and a filter built on it returns 200 with an empty result and no explanation.
That gap is what POST /audience/coverage exists to close: per attribute, how many contacts have it filled, how many distinct values it holds, and what those values actually are.
Some keys are not attributes
A handful of keys accepted by attribute_condition are not project attributes at all: a universal search field, the sampling bucket, and other computed handles. They behave like attributes in a filter but never appear in the catalogue. They are documented with the grammar, in Dynamic attributes.
What's next
- Events: the other half of what a contact carries.
- Probes: which of these attributes actually hold data, and what values they hold.
- Query filter: how to write a condition on an attribute once you know it is usable.
- Counting and retrieval: fetching a single contact by id, user id, phone or email.