1. Profile import

Batch file format

Important

Feature availability is part of a phased rollout. Your organization may not see this functionality yet. It will become available when your environment is included in the rollout.

Profile import uses the JSON Lines (JSONL) format for both input and output files. Each line in the file is a standalone JSON object. This topic describes the structure of input records, how identity resolution works, update behavior, and the format of output results.

Input file format

Input files must meet the following requirements:

  • Encoding: UTF-8.
  • Format: JSON Lines (JSONL). One JSON object per line, with no trailing commas.
  • Size: Maximum 100 MB per file.

Each line in the input file represents a single record to import.

Record structure

Each record must contain the following required fields:

FieldTypeRequiredDescription
recordTypeStringYesThe type of record. Use profile for profile records. Case-insensitive.
identifiersArrayYesAn array of identifier objects, each with a provider and id field. Identifier(s) must have a provider that matches a configured identity rule and a non-empty id.

Each record must contain at least one of the following fields with data:

FieldTypeRequiredDescription
contactObjectNoContact information for the profile, such as firstName, lastName, and email.
extensionsObjectNoCustom attributes for the profile. Use this field for any additional data your organization tracks.
Important

The record payload must contain at least one non-null field in contact or extensions. Records with no updatable fields fail with the NO_UPDATABLE_FIELDS error code.

Array values in the record payload are not supported. Any record that contains an array value in contact or extensions fails with the INVALID_RECORD error code.

The following tables describe the fields available in the contact and extensions objects:

profile.contact

FieldTypeDescriptionAllowed / FormatExample
addressobjectA structured representation of a physical location, including street, city, state, and postal code.See address
dateOfBirthdateThe date of birth formatted in ISO 8601 standard, representing the year, month, and day.ISO 8601 (YYYY-MM-DD)"1995-01-01"
emailstringThe contact's email address.RFC 5321"[email protected]"
firstNamestringThe individual's first name.Non-empty string (1–500 characters)"John"
genderstringThe gender of the individual. Free text is accepted, but one of the suggested values is recommended.Suggested: male | female | unknown | other (1–100 characters)"male"
languagestringThe language code represented in ISO 639-1 format, using two uppercase letters.ISO 639-1 uppercase (2 letters)"EN"
lastNamestringThe individual's last name.Free text (1–500 characters)"Smith"
phoneNumbersobjectA structured representation of various phone numbers, including home, work, and mobile.See phoneNumbers{ "home": "", "work": "", "mobile": "" }
titlestringA title for the individual, which can be free text or one of the specified enumerated values.Suggested: "Br", "Brigadier", "Capt", "Colonel", "Dame", "Dr", "Elder", "Fr", "General", "Hon", "Judge", "Lord", "Master", "Miss", "Mr", "Mrs", "Ms", "Mstr", "Prof", "Rabbi", "Rev", "Shaikha", "Sheikh", "Sir", "Sister", "Sr" (1–100 characters)"Mr"

profile.contact.address

FieldTypeDescriptionAllowed / FormatExample
citystringThe name of the city.Free text (1–500 characters)"Madrid"
countrystringThe country code.ISO 3166-1 alpha-2 (required if address is present)"ES"
postCodestringThe postal code, formatted according to the country.Country-appropriate (1–500 characters)"AB 123"
statestringThe name of the state or region.Free text (1–500 characters)"Colorado"
streetstringThe street address.Free text (1–500 characters)"12 Beaker Street"

profile.contact.phoneNumbers

FieldTypeDescriptionAllowed / FormatExample
homestringHome phone number.E.164 or empty"+34911222333"
workstringWork phone number.E.164 or empty"+34914445566"
mobilestringMobile phone number.E.164 or empty"+353861234567"

profile.extensions

General-purpose extensible attributes defined by your organization to meet specific business needs. You can include any number of custom key-value pairs directly inside the extensions object. The field names are defined by you, not by the API.

FieldTypeDescriptionAllowed / FormatExample
<your-field-name>string, boolean, number, objectA custom field with a name and value defined by your organization.string, boolean, number, object"extensions": {"tier": "gold", "score": 42}

Input example

The following example shows a JSONL file with 12 profile records:

{"recordType":"profile","identifiers":[{"provider":"email","id":"[email protected]"}],"contact":{"firstName":"Alice1","lastName":"Smith2"}, "extensions":{"where": "In the wonderland"}}
{"recordType":"profile","identifiers":[{"provider":"email","id":"[email protected]"}],"contact":{"firstName":"Alice2","lastName":"Smith2"}, "extensions":{"where": "In the wonderland1"}}
{"recordType":"profile","identifiers":[{"provider":"email","id":"[email protected]"}],"contact":{"firstName":"Alice3","lastName":"Smith2"}, "extensions":{"where": "In the wonderland2"}}
{"recordType":"profile","identifiers":[{"provider":"email","id":"[email protected]"}],"contact":{"firstName":"Alice4","lastName":"Smith2"}, "extensions":{"where": "In the wonderland3"}}
{"recordType":"profile","identifiers":[{"provider":"email","id":"[email protected]"}],"contact":{"firstName":"Alice5","lastName":"Smith2"}, "extensions":{"where": "In the wonderland4"}}
{"recordType":"profile","identifiers":[{"provider":"userid","id":"crm-0042"},{"provider":"email","id":"[email protected]"}],"contact":{"firstName":"Bob","lastName":"Jones"}}
{"recordType":"profile","identifiers":[{"provider":"userid","id":"crm-0044"}],"contact":{"firstName":"James","lastName":"Red"}}
{"recordType":"profile","identifiers":[{"provider":"userid","id":"crm-0044"}],"contact":{"firstName":"James","lastName":"Red"}}
{"recordType":"profile","identifiers":[{"provider":"userid","id":"crm-0044"}],"contact":{"firstName":"James","lastName":"Red"}}
{"recordType":"profile","identifiers":[{"provider":"userid","id":"crm-0044"}],"contact":{"firstName":"James","lastName":"Red"}}
{"recordType":"profile","identifiers":[{"provider":"userid","id":"crm-0044"}],"contact":{"firstName":"James","lastName":"Red"}}
{"recordType":"profile","identifiers":[{"provider":"userid","id":"crm-0044"}],"contact":{"firstName":"James","lastName":"Red"}}

In this example:

  • Records 1–5 each use a single email identifier and include both contact and extensions.
  • Record 6 has two identifiers from different providers (userid and email) and omits the optional extensions field.
  • Records 7–12 are identical, each with a single userid identifier and no extensions field.

Identity resolution

When processing each profile record, profile import resolves the record's identifiers against existing profiles to determine whether to create a new profile or update an existing one. The order of the records in the file is not preserved while processing the file.

How identity resolution works

  1. The system checks only identifiers whose provider matches a configured identity rule for your environment. Identifiers with unrecognized providers are ignored during resolution.
  2. Identifier values are case-insensitive. For example, [email protected] and [email protected] resolve to the same profile.
  3. The system looks up each qualifying identifier to find matching profiles.

Resolution outcomes

Matching profiles foundActionDescription
0CreateA new profile is created with a system-generated profile ID.
1UpdateThe existing profile is updated with the data from the record.
More than 1FailThe record fails with the INCONSISTENT_IDENTIFIERS error code because the identifiers resolve to multiple different profiles.

Adding new identifiers to existing profiles

Important

You must create the identity rule in your SitecoreAI environment before you upload the batch. Identifiers with unrecognized providers are ignored.

When a record contains identifiers from multiple providers, profile import adds any new identifiers to the matched profile. This lets you associate a new identifier type with profiles that were previously identified by a different provider.

For example, if your SitecoreAI environment uses email as the default identifier and you later create an identity rule for userid (CRM ID), you can upload a batch where each record contains both an email and a userid identifier. Profile import resolves the record against the existing profile using the email identifier, then adds the userid as an additional identifier on that profile.

Update behavior

When a record matches an existing profile, the system updates the profile using deep-merge semantics:

  • Object fields are merged recursively. Incoming fields are added to or overwrite existing fields. Fields not present in the incoming record are preserved.
  • Null values remove the corresponding field from the profile.
  • Only contact and extensions are written to the profile.
  • If the incoming data is identical to the existing profile and there are no missing identifiers to add, no write occurs. The profile's modifiedAt timestamp is not updated.

Example

If the existing profile contains:

{
  "contact": {
    "firstName": "Alice1",
    "lastName": "Smith2"
  },
  "extensions": {
    "where": "In the wonderland"
  }
}

And the incoming record contains:

{
  "contact": {
    "email": "[email protected]"
  },
  "extensions": {
    "where": "Wonderland"
  }
}

The resulting profile is:

{
  "contact": {
    "firstName": "Alice1",
    "lastName": "Smith2",
    "email": "[email protected]"
  },
  "extensions": {
    "where": "Wonderland"
  }
}

In this example, firstName and lastName are preserved because they were not included in the incoming record; email is added as a new field; extensions.where is overwritten with the new value.

Updating extension data

Data extensions hold your business-specific key-value data. On update, keys are merged one by one.

If the existing profile contains:

{
  "extensions": {
    "loyaltyTier": "Silver",
    "where": "In the wonderland"
  }
}

And the incoming record contains:

{
  "extensions": {
    "where": "Wonderland",
    "favoriteColor": "Blue"
  }
}

The resulting profile is:

{
  "extensions": {
    "loyaltyTier": "Silver",
    "where": "Wonderland",
    "favoriteColor": "Blue"
  }
}

In this example, loyaltyTier is preserved because it wasn't in the incoming record; where is overwritten with the new value; favoriteColor is added as a new field.

Adding identifiers

The identifiers array is what recognizes a guest across systems. Each entry has a provider and an id. Sending a new identifier adds it alongside the existing ones rather than replacing the array. If the existing identifier is sent with a new id, the system treats is as a new profile.

If the existing profile contains:

{
  "identifiers": [
    {
      "provider": "email",
      "id": "[email protected]"
    }
  ]
}

And the incoming record contains:

{
  "identifiers": [
    {
      "provider": "email",
      "id": "[email protected]"
    },
    {
      "provider": "crm",
      "id": "CRM-00042"
    }
  ]
}

The resulting profile is:

{
  "identifiers": [
    {
      "provider": "email",
      "id": "[email protected]"
    },
    {
      "provider": "crm",
      "id": "CRM-00042"
    }
  ]
}

The existing "email" identifier stays, and the new "crm" identifier is appended. From here, identity resolution (via the IDENTITY event) can match profiles with added identifiers, in the order defined in identity rules.

Note that you must create an identity rule with a new identifier before uploading a batch with the new identifiers.

Delete behavior

Deleting a single field

To delete a single field inside an object, set that key to null. Only the nulled key is removed; all other fields in the object are preserved.

If the existing profile contains:

{
  "contact": {
    "firstName": "Alice1",
    "lastName": "Smith2",
    "email": "[email protected]"
  }
}

And the incoming record contains:

{
  "identifiers": [
    { "provider": "email", "id": "[email protected]" }
  ],
  "contact": {
    "firstName": "Alice1",
    "lastName": null,
    "email": "[email protected]"
  }
}

The resulting profile is:

{
  "identifiers": [
    { "provider": "email", "id": "[email protected]" }
  ],
  "contact": {
    "firstName": "Alice1",
    "email": "[email protected]"
  }
}

In this example, lastName is deleted because it was set to null. firstName and email are preserved because they were not nulled. The same pattern applies to a key inside extensions.

Deleting an entire object

To delete an entire object, set either contact or extensions to null. Do not set both to null. The identifier must resolve to an existing profile, and at least one of contact or extensions must remain in the profile after the update.

If the existing profile contains:

{
  "identifiers": [
    { "provider": "email", "id": "[email protected]" }
  ],
  "contact": {
    "firstName": "Alice1",
    "lastName": "Smith2"
  },
  "extensions": {
    "loyaltyTier": "Silver",
    "where": "Wonderland"
  }
}

And the incoming record contains:

{
  "identifiers": [
    { "provider": "email", "id": "[email protected]" }
  ],
  "contact": {
    "firstName": "Alice1",
    "lastName": "Smith2"
  },
  "extensions": null
}

The resulting profile is:

{
  "identifiers": [
    { "provider": "email", "id": "[email protected]" }
  ],
  "contact": {
    "firstName": "Alice1",
    "lastName": "Smith2"
  }
}

In this example, the entire extensions object is removed because it was set to null. contact is preserved.

At least one object is required

A record that sets both contact and extensions to null is rejected. At least one of contact or extensions must contain data for the record to be processed.

The following record fails with the NO_UPDATABLE_FIELDS error code:

{
  "identifiers": [
    { "provider": "email", "id": "[email protected]" }
  ],
  "contact": null,
  "extensions": null
}

The following constraints apply to all delete operations:

  • Every record must include a valid identifier. An identifier with a null or unrecognized provider is ignored during resolution, and the record fails if no valid identifier remains.
  • At least one of contact or extensions must be present with content. Setting both to null returns the NO_UPDATABLE_FIELDS error code.

Output file format

After a batch reaches a final state (COMPLETED, COMPLETED_WITH_ERRORS, or FAILED), you can view the results in the Profile import page or download the results file using the following API request:

curl "{base_url}/v1/batches/{batchId}/results" \
  -H "Authorization: ApiKey <your-api-key>" \
  -o results.jsonl

Output record structure

Each output record contains the following fields:

FieldTypeDescription
recordIndexIntegerThe zero-based index of the record in the input file.
idString (UUID)The correlation ID from the input record, if one was provided.
recordTypeStringThe record type from the input record, such as profile. Omitted if the record failed before the type could be parsed.
outcomeStringThe result of processing: CREATED, UPDATED, or FAILED.
profileIdString (UUID)The profile ID. Present for CREATED and UPDATED outcomes.
errorCodeStringThe error code. Present only for FAILED outcomes. See Record-level error codes.
errorDescriptionStringA human-readable description of the error. Present only for FAILED outcomes.

Output example

{"recordIndex":7,"recordType":"profile","outcome":"FAILED","errorCode":"INVALID_RECORD","errorDescription":"No identifier with a valid id and a recognised provider"}
{"recordIndex":8,"recordType":"profile","outcome":"FAILED","errorCode":"INVALID_RECORD","errorDescription":"No identifier with a valid id and a recognised provider"}
{"recordIndex":6,"recordType":"profile","outcome":"FAILED","errorCode":"INVALID_RECORD","errorDescription":"No identifier with a valid id and a recognised provider"}
{"recordIndex":9,"recordType":"profile","outcome":"FAILED","errorCode":"INVALID_RECORD","errorDescription":"No identifier with a valid id and a recognised provider"}
{"recordIndex":10,"recordType":"profile","outcome":"FAILED","errorCode":"INVALID_RECORD","errorDescription":"No identifier with a valid id and a recognised provider"}
{"recordIndex":11,"recordType":"profile","outcome":"FAILED","errorCode":"INVALID_RECORD","errorDescription":"No identifier with a valid id and a recognised provider"}
{"recordIndex":2,"recordType":"profile","outcome":"CREATED","profileId":"f66bf7c3-79e0-45f4-9862-777db6a1fe7d"}
{"recordIndex":5,"recordType":"profile","outcome":"UPDATED","profileId":"3458e2eb-447a-4b41-a664-86fc4ce8564f"}
{"recordIndex":3,"recordType":"profile","outcome":"CREATED","profileId":"29c63a5a-ef15-472c-a784-e1f16f4d80f7"}
{"recordIndex":0,"recordType":"profile","outcome":"UPDATED","profileId":"a1ee5416-f002-4290-aa36-1f89abc3b32f"}
{"recordIndex":1,"recordType":"profile","outcome":"UPDATED","profileId":"468154b0-98f6-4bb1-8e16-a5e6b53624bb"}
{"recordIndex":4,"recordType":"profile","outcome":"CREATED","profileId":"a51637cd-da73-4967-b0ee-4210b2cf4b37"}
If you have suggestions for improving this article, let us know!