Attributes

Beyond the base record, a profile carries whatever fields your organization has defined: a company, a cohort, an account manager. Your workspace owns that schema. A field is created once, by an admin in the app or through the API, and your integration then writes values into it.

Values arrive in the custom object of a record on /ingest/contacts, keyed by field key. Ingest never creates fields on its own, so a typo in a key is a loud error rather than a new column.

The field registry

GET/contacts/fields

Every field with its key, label, type and, for categorical fields, options. Only fields with "source": "manual" can be written by ingest. The others are computed — event aggregations (lifetime revenue, last session…), calculated fields, scores — and are recalculated by OpenHouse; writing to one is refused, because the next recalculation would silently overwrite it.

Creating a field

POST/contacts/fields
{
  "key": "cohort",
  "label": "Cohort",
  "type": "categorical",
  "options": ["2026-Q3", "2026-Q4"]
}
key
string
camelCase identifier, 2–41 characters, letter first: ^[a-zA-Z][a-zA-Z0-9_]{1,40}$. This is the key you send in custom.
label
string ≤80
Display name in the app.
type
text | number | boolean | date | categorical | list
text ≤2000 chars; number is a JSON number; boolean is true/false; date is an ISO 8601 string; categorical is one of options (exact match); list is an array of strings — an open SET (unique, each ≤80 chars, at most 100) for attributes with no date or history, like languages or tags. Something a profile joins or does over time is an event, not a list.
options
string[]
Required for categorical fields, 1–50 values. Not allowed on list fields — a list is open.

Writing one value

PATCH/contacts/:id/fields/:key
{ "value": ["de", "fr"], "mode": "add" }
value
typed
The value in the field's type; null clears it.
mode
set | add | remove
list only. set (the default, and the only mode for every other type) replaces the stored list; add puts the given values in — unique, a re-added value moves to the end, and past 100 values the oldest are dropped; remove takes them out, and a list emptied this way is cleared. Both are applied in the database, so two writers never overwrite each other's edits.

In custom on /ingest/contacts a list value is always the full list ("languages": ["de", "fr"]) and replaces what is stored — an ingest record carries the whole profile. In a CSV the cell is de; fr (split on ;). Values are stored as sent: en and EN are two values, so agree on a spelling in your integration.

What happens to a bad custom value

  • A key that is unknown, computed or archived fails the whole request with a 400 that names the field and which of the three it was. Keys are schema: nothing is written until every key in the batch resolves.
  • A value that doesn't fit the field's type ("12" for a number field, an option that isn't in the list) is reported per record in fieldErrors — [{ "index": 1, "field": "cohort", "reason": "Value must be one of: …" }] — while the record itself, its base fields and its other custom values still land. A bad cell is not a bad row.
  • null, an empty string, or a key left out never erase what is stored — the same rule as the base fields. Clearing a value is a deliberate act in the app.

A dynamic CRM. Your audiences, campaigns and reports keep up.

Customer data, analysis and campaigns usually live with three vendors, and an agent cannot run an account whose pieces do. In OpenHouse they are one product, so a question becomes a segment becomes a campaign, in one conversation.

Customer data platform

Events from the shop, the app and payments land in one profile per customer. Attributes and segments are derived from them.

CRM with a design studio

Campaigns and automations run on those segments. Emails are designed in the same place, on the brand.

Self-service BI

Anyone on the team asks a question of the customer base and gets the chart. Dashboards are described, not built.

Let’s talk.

Access is by invitation. If your team already runs a workspace, an administrator there can invite you. Otherwise, request a demo and we will set one up with you.