Send customer profiles
A customer profile is JustAI’s record of one person. It holds your user ID for that person and their traits, such as email address, first name, and plan. Journeys, segments, and message personalization read these traits. You create and update profiles with the Identify API. This page shows how to send an Identify request, how JustAI merges each update into the profile, and how to read the saved profile back.
Segment, RudderStack, and Hightouch send the same Identify request for you, so the rules on this page apply to them too. See Choose a data source.
Before you begin
Section titled “Before you begin”- Create an API key while the environment you send to is selected. Open the account menu, select Settings > API keys, and select New key. Copy the key when it is shown, because JustAI shows it only once. If New key reports that key creation is restricted, ask your JustAI team to enable API access for your organization.
- Get your organization slug from your JustAI team.
- Agree which value you send as the user ID. See Identity and consent.
An API key works for every JustAI API in its environment. Keep it on your servers or in your CDP or warehouse tool. Never put it in browser or mobile app code.
Send an Identify request
Section titled “Send an Identify request”Send the profile in a POST request:
POST https://ingest.justwords.ai/api/identify/{org_slug}X-Api-Key: YOUR_API_KEYContent-Type: application/json
{ "userId": "user-1234", "messageId": "user-1234:2026-10-12T15:00:00Z", "timestamp": "2026-10-12T15:00:00Z", "traits": { "email": "ada@example.com", "first_name": "Ada", "plan": "pro", "signup_date": "2026-09-30" }}The first request for a userId creates the profile. Later requests update it. JustAI saves the update before it responds, and returns 200:
{ "success": true, "messageId": "user-1234:2026-10-12T15:00:00Z", "duplicate": false}Most first-time errors come from these rules:
- Send one profile per request. An array or a
{"batch": [...]}body returns400. - Do not end the URL with a slash. A trailing slash returns
404. - Send only identify calls to this endpoint. Identify does not check the call type, so a Group call from a CDP writes the group’s traits onto the person’s profile.
- For production, omit the
X-JustAI-Environmentheader. For another environment, use its path value in place of{org_slug}, and send its environment key in theX-JustAI-Environmentheader. Your JustAI team gives you both values. See Environments.
Request fields
Section titled “Request fields”| Field | Required | Description |
|---|---|---|
userId |
Yes | Your stable ID for the person, as a string. JustAI removes leading and trailing spaces. |
traits |
Yes, unless you send consent |
An object of traits. Values can be strings, numbers, booleans, null, arrays, or objects. Send at least one trait. |
messageId |
No | A unique ID for this update. If you omit it, JustAI generates one and returns it. See Message IDs and safe retries for when to omit it. |
timestamp |
No | When the values changed in your system, in ISO 8601 with Z or an offset. If you omit it, JustAI uses the time it receives the call. |
consent |
No | Email and SMS consent decisions. See Identity and consent. |
Put traits in the top-level traits object. JustAI does not read context.traits, and it does not save context, integrations, anonymousId, or any other top-level field. For every validation rule, see the Identify API reference.
Include the email trait
Section titled “Include the email trait”Send each person’s email address as traits.email, with the key in lowercase. JustAI uses it to:
- Reach the person in Iterable. JustAI identifies every recipient to Iterable by this address, for email, SMS, and push. A person without an email address can’t receive messages through Iterable.
- Apply email consent decisions.
- Show the address on the person’s page in Users.
To record SMS consent decisions, also send the phone number as traits.phone.
How updates merge
Section titled “How updates merge”Each Identify call merges into the stored profile. It never replaces the whole profile.
- JustAI writes only the top-level traits you send. Traits you leave out keep their values.
- A nested object or array replaces the whole stored value for that trait. JustAI does not merge nested values.
- Sending
nullstoresnull. The trait stays on the profile with anullvalue. - Identify can’t delete a trait. To delete a person’s data, contact your JustAI team.
For example, a profile has these traits:
{ "email": "ada@example.com", "plan": "trial", "address": { "city": "London", "country": "GB" }}You send this update:
{ "userId": "user-1234", "traits": { "plan": "pro", "address": { "city": "Paris" } }}The stored traits become:
{ "email": "ada@example.com", "plan": "pro", "address": { "city": "Paris" }}email is unchanged because the update left it out. address lost country because the new object replaced the old one. To change one nested value, send the whole object, or keep traits flat.
Timestamps and update order
Section titled “Timestamps and update order”JustAI records when each trait was last written. It writes a trait only when the call’s timestamp is the same as or later than that time. An older call does not overwrite a newer value, but the other traits in that call still apply. When two calls have the same time, the one that arrives last wins. Every one of these calls returns 200, so read the profile back to see which values JustAI applied.
Send timestamp as the time the values changed in your system. Without it, JustAI uses the time it receives the call. A delayed retry with no timestamp can then overwrite a newer value that arrived in between.
The timestamp must be an ISO 8601 string with a time zone, such as 2026-10-12T15:00:00Z or 2026-10-12T17:00:00.123+02:00. JustAI returns 400 for a time without a time zone, a space instead of T, an epoch number, a time before 1970, or a time more than 5 minutes ahead of JustAI’s clock. Identify timestamps lists every accepted and rejected form.
Message IDs and safe retries
Section titled “Message IDs and safe retries”A messageId identifies one update. Use a new one for every update, and reuse it only to retry the same call.
| You send | JustAI returns |
|---|---|
A new messageId |
200 with "duplicate": false. JustAI applies the update. |
The same messageId with the same userId, timestamp, and traits |
200 with "duplicate": true. JustAI applies nothing again. |
The same messageId with a different userId, timestamp, or traits |
409 with {"error":"messageId <id> was already used for a different call"} |
Send your own messageId on Identify when you can give every new update a new ID. Leave messageId out only when your sync tool can’t guarantee a new ID whenever a synced value changes. JustAI then generates a new ID for each call, so it can’t recognize a retry and treats every retry as a new update. When you leave messageId out, always send timestamp, so a delayed retry can’t overwrite a newer value.
For warehouse and reverse ETL syncs, build the messageId from values that change whenever any synced value changes, such as a hash of the user ID, the update time, and every synced trait. The Hightouch profile sync template builds it this way. An ID built only from user_id and updated_at returns 409 when a trait changes but updated_at doesn’t.
JustAI does not compare consent when it checks for a retry. When consent changes, use a new messageId. A call that reuses an earlier ID with the same userId, timestamp, and traits returns "duplicate": true and drops the new consent decision.
JustAI remembers each messageId per environment, so the same ID in production and in another environment does not collide.
If a call times out or returns 500, retry it with the same body and the same messageId. Fix the request before you resend a 400 or 409. See Errors and retries.
What profiles do in JustAI
Section titled “What profiles do in JustAI”Profile traits drive these parts of JustAI:
- Journeys. Confirm with your JustAI team whether A profile attribute changes is available for your organization. When available, an Identify call that changes at least one trait value can start that journey. A call that changes nothing, and a duplicate retry, trigger nothing. Wait until steps and exit rules can also check profile fields.
- Segments. Auto-updating segments in Users > Segments match people by their traits.
- Personalization. Messages read traits through Profile merge tags, written as
{{ profile.<field> }}.
Profile updates from a CSV upload change the traits but do not start profile-change journeys. Confirm the behavior of a customer-specific file import with your JustAI team. Use Identify for changes that must trigger a journey.
Consent decisions
Section titled “Consent decisions”Identify accepts an optional consent array. Each decision records a person’s email or SMS consent for an address on the profile. An email decision applies to traits.email, and an SMS decision applies to traits.phone. For the fields, the rules for which decision wins, and how consent relates to Iterable subscriptions, see Identity and consent. Agree your consent mapping with your JustAI team before launch.
Check your work
Section titled “Check your work”Confirm the saved profile with the Profiles API or in the JustAI app.
Read the profile with the Profiles API
Section titled “Read the profile with the Profiles API”Use the same API key, and the same environment header if you send one:
GET https://ingest.justwords.ai/api/profiles/{org_slug}/{user_id}X-Api-Key: YOUR_API_KEYURL-encode the user ID. For example, / becomes %2F and @ becomes %40. A known user returns 200 with the saved profile:
{ "org_slug": "acme", "user_id": "user-1234", "profile_id": "5f0c9a52-3e1b-4d7a-9c2e-8b6f1a2d4e90", "updated_at": 1791817200000, "fields": { "plan": "pro", "email": "ada@example.com", "first_name": "Ada", "signup_date": "2026-09-30" }}fieldsholds every saved trait.updated_atis the latest update time applied to the profile, in epoch milliseconds.profile_idis JustAI’s own ID for the profile.org_slugrepeats the slug or environment path value from the request.
An unknown user returns 404 with {"error":"Profile not found"}. The Profiles API finds people by user ID only, not by email.
A read right after an update can briefly return the previous values. If your change is not there, wait a minute and read again.
Find the person in the app
Section titled “Find the person in the app”-
In the JustAI app, check the account menu at the bottom of the left sidebar. It shows your organization and environment. Switch to the environment you sent to if needed.
-
Select Users and search for the user ID. The search matches user IDs, email addresses, and trait values.
-
Open the person. Attributes lists the User ID, the Email, and every other trait you sent.
-
Under Activity, open the Events tab. Each Identify call that changed a value has an entry. A call that changed nothing has no entry.
