Skip to content

Profiles

Upsert a user profile's traits and value score with PUT /api/v1/profile.

PUT /api/v1/profile writes traits (and an optional value score) to the same identified_users table the identify ingestion path uses. Use it when you have richer profile data to push than what naturally flows through identify — for example, a nightly job syncing computed lifetime-value scores.

PUT https://api.retidal.com/api/v1/profile

Auth: X-API-Key with scope profile:write.

Rate limit: 60 requests/minute per (project, IP).

Do not include projectId in the body — it’s always derived from the authenticated API key, and the request schema doesn’t accept it.

userIdbodystringrequired

Non-empty user/profile ID.

traitsbodyobjectrequired

Trait map, validated by the same rules as identify’s traits — see Identifying users: ≤ 8 KiB serialized, ≤ 2 levels nested, dangerous keys (like anything containing password or token) rejected.

valueScorebodynumber

Optional value score. Accepts null.

Request
{
"userId": "uid_456",
"traits": { "plan": "pro", "region": "apac" },
"valueScore": 87.5
}

The response is only returned after the D1 upsert completes — this is at-least-once, never fire-and-forget.

200
{ "ok": true }
Status Meaning
400 PUT /api/v1/profile: Invalid JSON, or invalid_payload (bad userId, traits, or valueScore, or a traits-sanitizer rejection — the response includes a detail string).
401 PUT /api/v1/profile: missing (missing_api_key) or unresolvable (unauthorized) API key.
403 PUT /api/v1/profile: the key lacks the profile:write scope (insufficient_scope).
404 PUT /api/v1/profile: cross-project key — tenant isolation (not_found).
429 PUT /api/v1/profile: rate limit exceeded (60/min per project + IP) (rate_limited).