Account relations
A B2B account graph lives on three relation shapes: member contacts ↔ a named account (contact↔account), parent ↔ child account (hierarchy), and person ↔ company (the contact-level link). The surface at Audience → Accounts reads all of them back from thegroup ingest events you send — there is no link-row table to manage. This guide walks the surface’s layout, the ingest calls that create and update each shape, who can read against it, and what flows downstream into scoring and suppression. The CDP account entity concept defines the underlying model; this is the operator walkthrough it links against.
1. Which relations exist
The graph is a projection overgroup events with no separate join table, so a “relation” is one of these shapes:
- Contact ↔ account membership. A contact belongs to an account when the ingest stream carries a
groupevent where the resolved contact joined the account’s explicitgroupId. The detail pane’s Members list and the profile-side relationship view in the account entity concept are both projections of these links. - Parent / child hierarchy (account → account). When an account’s golden record carries a
parent_group_id(or one of the alternate spellings the ship checks in order:parentGroupId,parent_account_id,parentAccountId,parent_account,parentAccount), that value names its parent and the children of the parent list back. You wire this by sending the trait on the samegroupcall that first identifies the account — no second endpoint. - Person-to-company. A member link between a resolved person profile and a named account. The contact’s
companyfield is display-level; the actual person↔company edge lives on thegrouplink above. - Contact ↔ account link objects. In the payload, each member edge is one JSON object:
contact_id,display_name,company,joined_at, andlast_seen_at— the per-contact row the Members table renders. TheGET /api/v1/cdp/contacts/:contact_id/relationshipsendpoint answers the inverse question, “which other contacts share the same accounts as this one?”, with a per-edgeshared_group_countweight.
group call with the right trait set.
2. Surface layout
The dashboard at Audience → Accounts puts the account list on one scrollable table and the graph context in a detail pane. Filter the list by members count, sort on any of the six fields (Members, Group activity, Last seen, First seen, Name, Account ID), and search by name or id. The detail pane opens when a row is selected and lays out:- Stat strip (Members / Group events / First seen / Last seen)
- Badge row — account id, plus a parent chip that re-opens the parent’s pane
- Golden record — every trait the survivorship rules merged
- Members — the contact↔account link objects described above, with a returned-of-total count
- Child accounts — navigable chips that re-open their pane
group events, so the timeline rows you review in a contact’s profile are the same rows the account graph folds.
3. Create a relation
Relation writes go through the CDP ingest lane — the same HMAC-signed lane every ingest route uses. The exact endpoint isPOST /cdp/v1/:ingest_id/group; the ingest-id lives on your CDP ingest secret and the route accepts the body listed in the CDP API reference.
groupId), the account row (a distinct non-empty groupId), and hierarchy (the parent trait). Updating a relation is the same call again — a later event that omits a trait never erases the established value, but a non-empty new value overwrites it (recency survivorship, last-write-wins per trait).
Graph side effects to plan for before posting:
- Each call stamps a
groupevent row, which is what powers the Members row’s “Joined”/“Last seen” and the account’s own First/Last seen — so the same call feeds both the relation edge and its timeline entry. - Because the golden record folds every
groupcall for the account, a batch of correct-shape calls is idempotent under survivorship: replaying an old state just re-wins the older traits. - A call without a parent trait leaves the hierarchy root-level. A call carrying the parent trait moves the account into its parent’s child list immediately; a later call with a different parent re-parents without keeping the old one.
4. Read vs write gating
Two gates separate readers from editors and they differ along the whole surface:- Reads. The Accounts surface and the two read endpoints gate on owner, admin, or developer plus the
contacts:readscope — the same gate that protects every /cdp surface that exposes PII-bearing traits. That means read is NOT open to the viewer role or the analyst/marketer seats: the surface is restricted on purpose. - Writes. Relation writes flow only through the signed ingest lane — your ingest secret holds the signing material, not any dashboard role. A member who can read the graph can still need the ingest secret in hand to change it.
5. Where relations cascade
Account links feed several downstream surfaces, and keeping the graph clean matters as much as keeping the account list:- Account scoring. The ranked list you’re browsing is the same projection the Account Scores view reads against for aggregation — list shape and score viewers stay consistent.
- Suppression and segments. The member link is the edge Segments and Suppression walk when an account-scoped filter evaluates “all contacts on this account”.
- Golden traits. Every attribute the detail pane shows — including industry, region, parent — sits in the survivorship fold, so downstream reads consistently see the traits you care about rather than whatever a mis-sent call last wrote.
6. API walkthrough
The same relations the dashboard renders come over the REST endpoints protected by a session or API key with thecontacts:read scope (plus the read role from section 4).
Ranked account list:
accounts/profilesacceptssort(members|events|recent|created|name|group_id),order(asc|desc),limit/offset(≤200), amin_membersfloor, and free-textqsearch.contacts/:contact_id/relationshipsacceptsmin_shared_groupsto raise the shared-tie threshold,limit(≤200),offset, andgroups_limitfor the memberships map. Return 404 when the contact id is not resolved in the workspace.- Callers hitting 429 should retry after the one-minute window — all three endpoints carry the same 60 per minute per tenant budget; stay idempotent and you stay safe.
7. Maintenance
The graph fixes itself or drifts itself — depending on how the ingest callers feed it. Mitigate drift at three points:- Naming. Trait keys priority-descend, so
parent_group_idbeatsparentAccount— pick one convention per SDK and the survivorship fold stops flipping. - Split-brain ids. Each distinct non-empty
groupIdbecomes an account row; trim whitespace/case sensitive id strings either at the SDK or with a transform function. - Hierarchy pull. Re-parenting an account moves its child list visibly at the next read, so check
accounts/profile?group_id=parentafter you wire a new parent trait.
group call with a corrected traits bag — or for deletion-of-record cases use the CDP erasure propagation lane against the member link.