> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# The contact record model

> What a contact row is in Devotel Orbit: the canonical person record every message send, campaign, flow, inbox conversation, and segment joins to, plus its identifiers, channel addresses, lifecycle, enrichment overlay, and erasure semantics.

# The contact record model

Every message you send, every campaign and flow you build, every inbox
conversation, and every segment you define resolves to a contact: one row
per person in your tenant, holding who they are, where they can be
reached, and what they consented to. This page defines that row. The
surrounding pages each cover one mechanism around it; this one covers the
record itself.

## What a contact is

A contact is the canonical identity anchor for a person inside one
tenant. Other pillars reference it by id and never copy it:

* A **campaign or batch send** selects contacts (directly or through a
  segment) and resolves a reachable address per channel at send time.
* A **flow** branches on contact fields and writes back to them.
* An **inbox conversation** is attached to a contact so an agent sees the
  person's history, not just the thread.
* The **CDP** layers traits, scores, and segments on top of the record.
* **Billing** meters outcome usage against the tenant, attributed per
  contact touch.

The record itself holds scalar profile fields (`first_name`,
`last_name`, `display_name`, `company`, `country_code`, `timezone`,
`language`), the channel addresses in the next section, your
`external_id` for the person, tags, and any custom fields you defined. It
deliberately does *not* hold message history, call records, or event
streams; those live on their own surfaces and join back to the contact id
(see [Timeline and journey](#timeline-and-journey)).

How this page relates to the fragments around the record:

* [Contact merge policy](/concepts/contact-merge-policy) defines how two
  contact rows collapse into one survivor.
* [CDP identity resolution](/concepts/cdp-identity-resolution) defines
  how likely-matches are proposed at scale before any merge runs.
* [Customer 360 snapshot model](/concepts/customer-360-snapshot-model)
  defines the read-composed dashboard view over the record.
* [Audience overview](/audience/overview) maps the operator console where
  you browse and edit contacts.

Read this page first if you are new to Orbit: it defines the noun the
other pages operate on.

## Identifiers

Every contact carries one immutable system id, assigned at creation:
`cnt_` followed by 32 lowercase hex characters, for example
`cnt_4f9a2c81d03b4e6a7f1c2b3d4e5f6071`. The id is never reassigned and
never reused: when two records merge, the survivor keeps its id; when a
record is erased, the id is tombstoned.

External identifiers index the record from the outside:

| Identifier | Shape | How it is used |
| - | - | - |
| `email` | Normalized email address | Import matching, dedupe grouping, email sends |
| `phone` | E.164, see [E.164 format](/concepts/e164-format) | Import matching, dedupe grouping, SMS and voice sends |
| `whatsapp_id` | WhatsApp business system user id or number | WhatsApp sends and inbound resolution |
| `viber_id` | Viber identifier | Viber sends |
| `external_id` | Your key (CRM id, warehouse key) | Integration-stable reference; the id your systems should store |
| Custom external ids | Any custom field you define | Tenant-specific matching and personalization |

Uniqueness is per tenant and per identifier type. Within a tenant, a
normalized email or phone identifies at most one live contact for
matching purposes: an import that arrives carrying an email or phone an
existing contact already holds resolves to that contact instead of
creating a second row, and the server-side duplicate scanner groups on
normalized email and phone (see
[Duplicates](#duplicates-two-collapse-mechanisms)). `external_id` is the
key we recommend your integrations store: unlike an address, it never
changes when the person changes handset or inbox, so it keeps resolving
after merges and re-imports.

## Channel addresses

One contact holds several reachable addresses at once: an SMS-capable
phone number, a WhatsApp identity, an email address, a Viber identity,
and push tokens. A send on a given channel resolves to that channel's
address on the record; when more than one candidate exists, the contact's
channel preferences and your messaging services decide which address is
used (see [sender resolution](/concepts/sender-resolution) and
[message send request lifecycle](/concepts/message-send-request-lifecycle)).

Consent attaches **per channel address, not to the record as a whole.**
A contact who opted in on SMS and never on email is sendable on one
channel and suppressed on the other. The consent state, the quiet-hours
and frequency-cap checks, and the suppression lists that enforce it are
defined in [consent and suppression model](/concepts/consent-and-suppression-model);
this page only fixes where consent lives: on the channel address of a
specific contact, never implied by the contact existing.

## Lifecycle

A contact moves through four states:

1. **Created.** Creation paths that write a contact row:
   * `POST /api/v1/contacts` from your backend or an SDK.
   * CSV and CRM imports, which upsert: a row whose identifier matches an
     existing contact is updated, not duplicated (see
     [imports migration model](/concepts/imports-migration-model)).
   * Inbound auto-create: a message or call from an address your tenant
     has never seen resolves to a new contact so the conversation has a
     person attached (see
     [inbound message resolution](/concepts/inbound-message-resolution)).
   * Form and widget captures, and CRM sync connectors.
2. **Active.** The normal state. Profile fields, tags, custom fields,
   and addresses are editable from the dashboard, the API, imports, and
   enrichment.
3. **Merged-into.** A duplicate is folded into a survivor. The secondary
   id stops being a live record and is kept as an alias that resolves to
   the survivor, so stored references keep working (see
   [Duplicates](#duplicates-two-collapse-mechanisms)).
4. **Erased.** A GDPR erasure tombstones the row (see
   [Erasure and retention](#erasure-and-retention)).

Who may act is scoped like every other tenant resource: dashboard roles
gate create, edit, merge, and erase in the console, and API keys carry
the scopes your integration was granted. The role and scope matrix is
[roles, teams, and permissions](/concepts/roles-teams-permissions).

## Duplicates: two collapse mechanisms

Two different mechanisms remove duplicate records, and they fire at
different points:

* **Deterministic merge** collapses one *pair* (up to 20 secondaries per
  request) into one survivor, per-field, with an audit row and a 30-minute
  undo window. You drive it: the duplicate scanner proposes groups on
  normalized email and phone, and you or your code post the merge. This
  is [contact merge policy](/concepts/contact-merge-policy).
* **Probabilistic identity resolution** proposes likely-matches at scale
  from the CDP side, feeding a review queue instead of merging directly.
  This is [CDP identity resolution](/concepts/cdp-identity-resolution),
  backed by the [identity and device graphs](/concepts/cdp-identity-graph-and-device-graph).

Use deterministic merge when you have a concrete pair you know is one
person; use identity resolution when you are scanning a whole audience
for records that *might* be the same person. Both write to the same
contact rows defined on this page.

## Enrichment

Enrichment is an overlay on the record, not a second record. The
enrichment surface derives traits a sender did not provide (for example
a timezone resolved from a phone number) and next-best-action fields
used by decisioning, and writes them onto the contact so every reader
sees them. Two rules keep the overlay honest:

* Enrichment does not silently overwrite values an operator or your API
  set by hand; manual edits keep precedence and carry their own history.
* Enriched traits are readable and filterable exactly like imported
  fields, so segments and personalization slots do not need to know
  where a value came from.

The inbound side of the same idea (deriving intent and traits from an
incoming message) is covered in
[inbound AI enrichment](/concepts/inbound-ai-enrichment).

## Timeline and journey

The per-contact **timeline** assembles the events that reference the
contact id into one ordered feed: sent and received messages, calls,
campaign touches, consent changes, and operator notes. The
**journey** view is the campaign- and flow-shaped cut of the same
events: which sends reached this person, with what outcome.

Neither is stored as a copy on the contact row; both are read-time
assemblies over the event surfaces, which is why a message deleted or
retired by a retention window disappears from the timeline too. The
[Customer 360 snapshot](/concepts/customer-360-snapshot-model) is the
third read over the same record: it composes the timeline, consent
state, scores, and open conversations into the drawer an agent sees.
Timeline and 360 differ only in composition, not in source data.

## Bulk operations and limits

For more than a handful of records, use the bulk surface instead of
looping single-record calls:

* Bulk tag, field update, and lifecycle-stage changes accept up to
  50,000 contact ids per request.
* Tag writes carry at most 25 tags per contact, each tag up to 50
  characters; name fields cap at 200 characters and timezone at 64.
* Bulk operations are idempotent by construction where it matters:
  re-applying the same stage change to a contact already in that stage
  is a no-op, so a retry after a timeout does not double-apply.
* CSV-scale ingestion runs as import jobs with their own history and
  matching rules rather than as one giant request (see
  [imports migration model](/concepts/imports-migration-model)).

The full request and response payloads, including the bulk and import
endpoints, are in the
[contacts API reference](/api-reference/endpoints/contacts).

## Erasure and retention

Erasure is the terminal state and is treated as a legal operation, not a
delete button: the request requires re-authentication, the profile
fields and channel addresses are scrubbed, references in histories are
anonymized, and the id is tombstoned rather than recycled. Two things
deliberately survive:

* **Suppression entries.** The person's addresses are projected onto the
  suppression surface so an erased contact can never be re-imported into
  a send. Suppression outliving the record is what makes the erasure
  durable (see
  [consent and suppression model](/concepts/consent-and-suppression-model)).
* **Held records.** Rows the law keeps independent of the contact, such
  as billing and consent audit trails, persist in their own surfaces
  under their own retention rules (see
  [retention windows and deletion](/concepts/retention-windows-and-deletion)).

The end-to-end data-subject workflow, including what a DSAR export
contains after a merge history exists, is
[DSAR concept](/concepts/dsar-concept).

## Worked example: one contact, four surfaces

1. **Inbound SMS creates the record.** A text arrives from
   `+14155550100`, an address your tenant has never seen. Inbound
   resolution creates a contact with that phone address, assigns a
   `cnt_` id, and attaches the conversation. Surface: messaging inbound
   plus the contacts record.
2. **Enrichment layers on.** The enrichment surface derives a timezone
   from the number and writes next-best-action fields onto the same
   row. Surface: enrichment overlay; the record gains traits without a
   second row appearing anywhere.
3. **A CRM import lands a duplicate, and merge collapses it.** Your
   nightly import carries the same person as `external_id: c-8841` with
   an email address. Import matching keeps both rows because no
   identifier overlapped; the duplicate scanner or identity resolution
   later proposes the pair; you post
   `POST /api/v1/contacts/merge` with the imported record as secondary.
   The survivor keeps its `cnt_` id, unions the email and
   `external_id` onto the record, folds the import's conversation
   history in, and writes an audit row. Surface: imports, then the
   deterministic merge.
4. **The person asks to be erased.** You issue the erasure with
   re-authentication. The profile and addresses are scrubbed, the id is
   tombstoned, the phone and email are projected onto suppression, and
   the merge audit remains available for disclosure. Surface: erasure
   plus suppression; the timeline now shows an anonymized history.

At no point did any pillar copy the person's data: every surface either
wrote to the one record or joined to its id.

## Related route surface

| Method | Path | Purpose |
| - | - | - |
| `POST` | `/api/v1/contacts` | Create one contact |
| `GET` | `/api/v1/contacts/duplicates` | Scan for duplicate groups (exact or fuzzy) |
| `GET` | `/api/v1/contacts/duplicates/count` | Aggregate duplicate count for banners |
| `POST` | `/api/v1/contacts/merge` | Fold secondaries into a survivor; returns `merge_id` |
| `POST` | `/api/v1/contacts/unmerge/:mergeId` | Revert one merge inside the 30-minute window |
| `GET` | `/api/v1/contacts/merge-history` | Audit rows for every merge and unmerge |

## See also

* [Contacts API reference](/api-reference/endpoints/contacts) - every
  endpoint and payload shape around the record
* [Contact merge policy](/concepts/contact-merge-policy) - the
  deterministic collapse mechanism
* [CDP identity resolution](/concepts/cdp-identity-resolution) - the
  probabilistic matching mechanism
* [Identity and device graphs](/concepts/cdp-identity-graph-and-device-graph) -
  the graph internals behind resolution
* [Customer 360 snapshot model](/concepts/customer-360-snapshot-model) -
  the composed read over the record
* [Consent and suppression model](/concepts/consent-and-suppression-model) -
  where per-channel consent and suppression live
* [DSAR concept](/concepts/dsar-concept) - export and erasure workflow
* [Custom fields model](/concepts/custom-fields-model) - extending the
  record with tenant-defined fields
* [Audience overview](/audience/overview) - the operator console for
  contacts


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.