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.
Endpoint
Section titled “Endpoint”PUT https://api.retidal.com/api/v1/profileAuth: 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.
Request body
Section titled “Request body”userIdbodystringrequiredNon-empty user/profile ID.
traitsbodyobjectrequiredTrait 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.
valueScorebodynumberOptional value score. Accepts null.
{ "userId": "uid_456", "traits": { "plan": "pro", "region": "apac" }, "valueScore": 87.5}Response
Section titled “Response”The response is only returned after the D1 upsert completes — this is at-least-once, never fire-and-forget.
{ "ok": true }Errors
Section titled “Errors”| 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). |
Next steps
Section titled “Next steps”Evaluate decision rules against the traits you write here.
The ingestion-side path that also writes to this same profile store.