Returning.AIDevelopers
v1

Guides / Broker Integrations

.md

Custom fields and milestones

A field definition, a saved user value, and a configured milestone evaluation are separate states. Define the field first, write a type-matched value for a resolved user, read the saved history and current value back, and only then check the configured milestone or widget projection.

Field value to widget state

Each step has a different owner and readback surface. Do not collapse them into one write.

  1. Define the field

    Community owner

    Create a stable field key and type with a community API key that has userFields permission.

  2. Resolve the user

    Broker server

    Use the platform user ID or another identifier accepted by the current user-first field route. CUSTOMER_1001 is not automatically a valid path ID.

  3. Save and read the value

    Broker server

    Write a typed value with the user-first history route, then read the narrow history/current projection before moving on.

  4. Evaluate configured milestone

    Returning.AI configuration

    A configured workflow, mission, or referral stage evaluates the saved state separately. Timing and reward completion are not promised by the field response.

  5. Refresh the widget

    Portal server

    After the authoritative state is ready, use the normal authenticated widget flow. A field write does not require a guessed milestone endpoint.

Follow the field lifecycle

StateWhat it meansReadback question
Definition and typeThe community has a stable key such as account_approved and a declared type.Does the configured field exist with the type your payload will send?
Saved value and historyA user-first history write accepted a typed value for one resolved user.Does the narrow history read show the expected value and action?
Configured milestoneA separate workflow, mission, or referral condition evaluates the saved state.Did the configured evaluator report the expected stage or qualification?
Widget projectionThe authenticated widget reads the current user state after the backend path completes.Does a fresh or refreshed widget view show the authoritative result?

Define a stable field

Use POST https://api.returning.ai/v1/communities/<COMMUNITY_OBJECT_ID>/user-fields with the server-side community API key and userFields permission. The display name is for people; data.field is the stable technical key used by later writes and reads. Confirm owner/admin access and the target type before creating the field.

define-field.sh
export RAI_API_BASE="https://api.returning.ai/v1"
export COMMUNITY_API_KEY="<COMMUNITY_API_KEY>" # server-side only
export COMMUNITY_OBJECT_ID="<COMMUNITY_OBJECT_ID>"

curl --fail-with-body --request POST \
  "$RAI_API_BASE/communities/$COMMUNITY_OBJECT_ID/user-fields" \
  --header "Authorization: Bearer $COMMUNITY_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Account Approved",
    "field": "account_approved",
    "type": "boolean"
  }'

The example intentionally leaves out defaultValue. Add a default only when the target field-definition contract confirms its type and behavior. Save the returned field object ID if your setup uses it, but the example below uses the stable field key.

field-definition-response.json
{
  "data": {
    "_id": "<FIELD_OBJECT_ID>",
    "name": "Account Approved",
    "field": "account_approved",
    "type": "boolean"
  }
}

Write a value for a resolved user

Resolve the trader from enrollment first. Use the returned <PLATFORM_USER_ID> in the user-first route below. CUSTOMER_1001 is the broker identity and is not automatically accepted as <userIdentifier> just because it was accepted as an active external ID or widget identifier.

The current public-shaped route is POST /v1/communities/<community>/users/<user>/user-fields/<field>/histories. It uses the same community API key family and the userFields permission, not SDK Access Key credentials.

write-account-approved.sh
export RAI_API_BASE="https://api.returning.ai/v1"
export COMMUNITY_API_KEY="<COMMUNITY_API_KEY>" # server-side only
export COMMUNITY_OBJECT_ID="<COMMUNITY_OBJECT_ID>"
export PLATFORM_USER_ID="<PLATFORM_USER_ID>"

curl --fail-with-body --request POST \
  "$RAI_API_BASE/communities/$COMMUNITY_OBJECT_ID/users/$PLATFORM_USER_ID/user-fields/account_approved/histories" \
  --header "Authorization: Bearer $COMMUNITY_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "value": true,
    "action": "overwrite"
  }'

The response excerpt below is the readback handle to inspect, not a promise of a full response shape. Confirm the status, field name, typed value, and action before checking any configured milestone.

field-write-response.json
{
  "meta": {
    "statusCode": 201
  },
  "data": {
    "_id": "<HISTORY_OBJECT_ID>",
    "createdAt": "2026-09-07T09:00:00.000Z",
    "fieldName": "account_approved",
    "value": true,
    "action": "overwrite"
  }
}

Keep values and actions type-matched

The current history body accepts strings, numbers, booleans, and supported arrays. The action defaults to overwrite. Use overwrite for the boolean state shown here; use increase or decrease only for a numerical field when the configured business rule calls for an additive change. A numeric zero must remain JSON number 0, not the string "0".

numerical-overwrite-body.json
{
  "value": 0,
  "action": "overwrite"
}

Do not copy a boolean payload into a numerical field, treat a false value as missing, or invent a select-value write example. Confirm the configured field type and accepted values before using any type that is not shown in the current operation contract.

Read the saved history

Read the same user and field after the write. Keep the request user-first and prefer the platform ID or another identity the route explicitly accepts. The current custom-field lookup path is not a universal substitute for this narrow history read.

read-field-history.sh
export RAI_API_BASE="https://api.returning.ai/v1"
export COMMUNITY_API_KEY="<COMMUNITY_API_KEY>" # server-side only
export COMMUNITY_OBJECT_ID="<COMMUNITY_OBJECT_ID>"
export PLATFORM_USER_ID="<PLATFORM_USER_ID>"

curl --fail-with-body --request GET \
  "$RAI_API_BASE/communities/$COMMUNITY_OBJECT_ID/users/$PLATFORM_USER_ID/user-fields/account_approved/histories?page=1&limit=5" \
  --header "Authorization: Bearer $COMMUNITY_API_KEY" \
  --header "Accept: application/json"

This request reads the newest five history rows, not the current field value. Match the returned write receipt's row ID andcreatedAt, user, field, typed value, and action; matching an oldtrue/overwrite row does not identify this write. If a newer write exists, account for it before judging the expected current value. The excerpt below is synthetic.

field-history-response.json
{
  "data": [
    {
      "_id": "<HISTORY_OBJECT_ID>",
      "createdAt": "2026-09-07T09:00:00.000Z",
      "fieldName": "account_approved",
      "value": true,
      "action": "overwrite"
    }
  ]
}

Read the current value separately

With the resolved platform ID, request the exact field key throughPOST /v1/users/info. This read requiresgetUserData, separate from the userFieldspermission on history operations. Replace account_approvedwith your configured key when using a different field.

read-current-field.sh
export RAI_API_BASE="https://api.returning.ai/v1"
export COMMUNITY_API_KEY="<COMMUNITY_API_KEY>" # requires getUserData

curl --fail-with-body --request POST "$RAI_API_BASE/users/info" \
  --header "Authorization: Bearer $COMMUNITY_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"idOrEmail":"<PLATFORM_USER_ID>","customFields":["account_approved"]}'
current-field-response-excerpt.json
{
  "data": {
    "userId": "<PLATFORM_USER_ID>",
    "customFields": { "account_approved": true }
  }
}

Check data.userId and the typed current value indata.customFields.account_approved, then compare with the latest confirmed business state. History proves a recorded mutation; this selected-field read checks the current projection. Neither proves milestone evaluation or reward credit.

Check the configured milestone separately

A field can be used by a configured onboarding, KYC, deposit, or referral condition, but the field definition does not create that condition. If the community has configuredaccount_approved as a boolean milestone input, write the real broker state and then wait for the configured evaluator. Do not write a fake value to force a reward.

  1. Confirm the definition and type for the configured field.
  2. Write the real business state with the resolved user identity.
  3. Identify the mutation in newest-first history, then separately read and record the current value.
  4. Check the configured workflow, mission, or referral read surface for evaluation.
  5. Only after the authoritative state is ready, verify the authenticated widget projection.

The milestone read reference exists, but an endpoint listing is not a successful acceptance example for your quest. Ask the Returning.AI setup owner for the existing configured IDs, the supported request for that user and a recorded successful result. The public reference marks the successful progress envelope as unproven without those inputs; do not invent a completion field or infer completion from a field write.

For an already-open widget, verify the same signed-in user and saved state first, then use the supplied bundle's confirmed refresh action and record the visible result. Renewing its token does not prove data refreshed. If the display action or signal is unconfirmed, keep widget acceptance open in the handover sheet; do not resend a write or award to refresh the screen.

If account_approved later changes back to false, your rewards owner and Returning.AI setup owner must agree whether completion stays earned, reopens or changes rewards. Test that configured outcome before enabling the rule. This guide does not prescribe reversal or clawback behavior.

No direct milestone write

This flow does not call a guessed /users/milestones/ endpoint. A milestone is a configured evaluation reached through the approved user, field, event, or workflow path. The platform does not publish a universal completion time for that evaluation or the resulting widget refresh.

Recover safely from uncertain writes

A timeout, 5xx response, or partial side effect does not make a repeated field mutation safe. Read the narrow history and current projection first. For increase or decrease, never blindly add the same change again. Repeated overwrite calls can still create history or downstream evaluation side effects, so an ambiguous result goes to the integration owner after bounded readback rather than an automatic replay.

  • Keep the original user, field, value, and action together when investigating an uncertain result.
  • Do not add a retry header that the current public operation does not document.
  • If the value is saved but the milestone is not visible, reconcile evaluation separately; do not rewrite the value just to poll.
  • Keep successfully saved state and failed downstream qualification as separate records for launch review.

Continue to launch checks

Open the launch checklist for owners, evidence, pause/replay rules, and the cases where a saved value still needs a separate configured readback. For the widget-side identity and token contract, use the custom widget quickstart.

Operation references

Use the current operation pages for the full route details: create a field definition, update a user field value, and list user field history, and read selected current user fields. The links describe the operation; your community owner still confirms the configured field and identity mapping.