Skip to main content

How guest profiles are shaped in v1 and v3

The flat v1 profile against the nested v3 profile, when to use create rather than ingest, and the deduplication rules to know before two systems write profiles.

The same guest looks different depending on which API you are talking to. v1 treats a profile as a flat set of fields. v3 treats it as a nested record where contact details are collections. This is the difference that most often surprises people porting code from one API to the other, or writing profiles from two systems at once.

If you have not chosen an API yet, start with Which Bookboost API should I use?

The v1 shape

In v1, a profile is a flat JSON object. You send the fields at the top level, and that is the whole request body. One email address, one phone number, no wrapper.

{
"first_name": "Maria",
"last_name": "Lindqvist",
"email": "maria@example.com",
"phone": "+46701234567",
"language_code": "sv"
}

The v3 shape

In v3, the same guest is nested. Everything sits inside a top-level data object, and contact details become arrays.

{
"data": {
"_identifier": { "type": "profile" },
"_source": { "entity_id": "crm-88213" },
"first_name": "Maria",
"last_name": "Lindqvist",
"language_code": "sv",
"emails": [
{
"_identifier": { "type": "email" },
"is_primary": true,
"address": "maria@example.com"
}
],
"phones": [
{
"_identifier": { "type": "phone" },
"is_primary": true,
"raw": "+46701234567",
"cca2": "SE"
}
]
}
}

Three things to remember about v3

  • Everything is wrapped in data. Forget the wrapper and you get a 400, not a validation error telling you which field was missing. If a request is rejected and the message is unhelpful, check the wrapper first.

  • Contact details are collections, not fields. A guest can have many email addresses and many phone numbers, and exactly one of each may be marked is_primary.

  • _source.entity_id is your ID, not ours. It is how you refer to a record from your own system, and it is what makes re-sending the same record an update rather than a duplicate. Choose it once, from a stable field in your own database, and never change it.

Create or ingest

v3 gives you two ways to write a profile, and they behave differently on the second attempt.

POST /cdp/public/v3/profiles
POST /cdp/public/v3/profiles/ingest

The create endpoint is strict. Send the same _source.entity_id twice and the second call fails with a 409. Use it for interactive flows, where a person is waiting and you need to know the outcome immediately.

The ingest endpoint is the upsert. It accepts the same ID repeatedly, returns a 202 straight away, processes in the background, and will not overwrite newer data with older data. Use it for syncing, where volume matters more than an immediate answer.

Because ingest returns before the work is done, a 202 means accepted rather than applied. If your sync needs to confirm the result, read the profile back rather than treating the 202 as proof.

The same guest has the same ID in both APIs

A profile's uuid in v1 and its _identifier.id in v3 are the same value, and the same is true for lists. You can create a guest with v1 and read them back with v3, or the other way round, with no mapping table in between.

This is what makes a split integration practical. A website form can write through v1 so that consent is recorded, and your own app can read the same guest through v3 for the richer data.

How each write path deduplicates

There are three write paths across the two APIs, and all three apply a different rule. This is worth knowing before you build, because the failure shows up as duplicate guests months later rather than as an error at the time.

POST /api/v1/users                 guards on email and phone
POST /api/v1/users/store-in-list no deduplication at all
POST /cdp/public/v3/profiles keys on _source.entity_id

  • The v1 users endpoint refuses to create a guest whose email address or phone number already exists, returning a 409, unless you pass force_create.

  • The v1 store-in-list endpoint does not deduplicate at all.

  • The v3 write endpoints deduplicate on your _source.entity_id and ignore email entirely.

So a guest who already exists can be blocked, duplicated, or updated, depending only on which path wrote them.

Deciding which system owns creation

If more than one of your systems writes profiles, pick one to own creation and let the others only update existing records. That single decision removes most duplicate problems before they start.

A common arrangement is for the system that first meets the guest, usually the PMS or the website form, to own creation, with everything else matching on the ID it produced.

Getting help

Open Help at the bottom of the left menu and choose Talk to Us, or email support@bookboost.io. If you think duplicates have already been created, tell us roughly when and from which system, and we can look at what happened before you plan a clean-up.

Did this answer your question?