Skip to main content

User Onboarding

Overview

After account creation, users progress through a configurable series of onboarding steps. Each step represents a task the user must complete (or that can be skipped by platform configuration) before they are fully onboarded. The onboarding system is:
  • Step-based — a linear sequence of steps, each with a status
  • Configurable — steps can be enabled or disabled per-user by the platform
  • Idempotent — submitting the same step twice is a no-op, not an error
  • Event-sourced — every transition is recorded as an auditable event

Quick Start

1. Create an account

curl -X POST /v0/user/ \
  -H "x-juno-jwt: <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "primary_eoa_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD78",
    "chain_id": 8453
  }'
Onboarding starts automatically. The response includes the user profile (201 Created).

2. Check onboarding state

curl /v0/user/me?expand=onboarding \
  -H "x-juno-jwt: <token>"
The onboarding field shows the current step, all steps with their status, and whether onboarding is complete.

3. Complete steps

For each step that requires user action, call submit when the user finishes:
curl -X POST /v0/user/me/onboarding/submit \
  -H "x-juno-jwt: <token>" \
  -H "Content-Type: application/json" \
  -d '{"step": "phone_verification"}'

4. Onboarding complete

When all steps are done, is_complete becomes true and current_step becomes "complete".
curl /v0/user/me?expand=onboarding \
  -H "x-juno-jwt: <token>"
# → { "onboarding": { "current_step": "complete", "is_complete": true, ... } }

Onboarding State

Reading State

Fetch the current onboarding state by expanding the onboarding field on the user profile:
GET /v0/user/me?expand=onboarding

Response Shape

{
  "user": { ... },
  "onboarding": {
    "current_step": "kyc_verification",
    "is_complete": false,
    "steps": [
      { "step": "phone_verification", "status": "completed", "gated": true },
      { "step": "kyc_verification",   "status": "current",   "gated": true },
      { "step": "open_banking",       "status": "pending",   "gated": true },
      { "step": "card_setup",         "status": "pending",   "gated": true },
      { "step": "feature_selection",  "status": "pending",   "gated": true }
    ]
  },
  "_links": { ... }
}
FieldTypeDescription
current_stepstringThe step the user is currently on, or "complete"
is_completebooleantrue when all steps are finished
stepsarrayAll onboarding steps in order
steps[].stepstringStep identifier
steps[].statusstringOne of: pending, current, submitted, completed, skipped
steps[].gatedbooleanWhether this step is configurable (can be skipped by the platform)

Step Statuses

StatusMeaning
pendingStep has not been reached yet
currentUser is on this step now — action required
submittedUser submitted this step (transient — auto-advances immediately)
completedStep finished successfully
skippedStep was skipped by platform configuration

State Machine

Any gated step that is disabled will be automatically skipped during advancement. The state machine jumps to the next enabled step (or to complete if all remaining steps are skipped).

Endpoints

Check Onboarding State

GET /v0/user/me?expand=onboarding
Returns the user profile with onboarding state included. See Response Shape above for the full structure. Response: 200 OK

Submit Step Completion

POST /v0/user/me/onboarding/submit
Use when the user has completed an action for the current step (e.g. verified their phone, completed KYC, made a selection). Request body:
{
  "step": "phone_verification"
}
Response: 200 OK
{
  "onboarding": {
    "current_step": "kyc_verification",
    "is_complete": false,
    "steps": [
      { "step": "phone_verification", "status": "completed", "gated": true },
      { "step": "kyc_verification",   "status": "current",   "gated": true },
      { "step": "open_banking",       "status": "pending",   "gated": true },
      { "step": "card_setup",         "status": "pending",   "gated": true },
      { "step": "feature_selection",  "status": "pending",   "gated": true }
    ]
  },
  "_links": { ... }
}
Behavior:
  • Records a step_submitted event, then immediately advances to the next step
  • If step does not match the user’s current step, returns the current state with 200 OK — no error, no state change
  • Calling submit on an already-completed step is a safe no-op

Advance Step

POST /v0/user/me/onboarding/advance
Use for server-driven or programmatic advancement where no user action needs to be recorded. Does not record a step_submitted event. Request body:
{
  "step": "open_banking"
}
Response: 200 OK — same shape as submit. Behavior:
  • Advances the user past the specified step without recording a submission event
  • Same idempotency rules as submit — mismatched step returns current state

Get Event History

GET /v0/user/me/onboarding/events
Returns the full chronological audit trail of onboarding events. Response: 200 OK
{
  "events": [
    {
      "step": "phone_verification",
      "event_type": "step_entered",
      "from_step": "created",
      "duration_ms": null,
      "created_at": 1700000000000
    },
    {
      "step": "phone_verification",
      "event_type": "step_submitted",
      "from_step": null,
      "duration_ms": null,
      "created_at": 1700000060000
    },
    {
      "step": "phone_verification",
      "event_type": "step_completed",
      "from_step": null,
      "duration_ms": 60000,
      "created_at": 1700000060000
    },
    {
      "step": "kyc_verification",
      "event_type": "step_entered",
      "from_step": "phone_verification",
      "duration_ms": null,
      "created_at": 1700000060000
    }
  ],
  "_links": { ... }
}
FieldTypeDescription
stepstringThe step this event relates to
event_typestringOne of: step_entered, step_submitted, step_completed, step_skipped
from_stepstring or nullThe previous step at the time of transition
duration_msinteger or nullTime spent in the step (only on step_completed events)
created_atintegerEvent timestamp (epoch milliseconds)

Step Reference

StepDescriptionRelated EndpointsAuto-advances?
phone_verificationVerify phone number via SMS codePUT /v0/user/me/phone (send code), PUT /v0/user/me/phone/code (verify)Yes — successful code verification auto-submits this step
kyc_verificationComplete identity verificationPOST /v0/kyc/start (begin KYC), GET /v0/kyc/status (check result)Yes — approved KYC status auto-submits this step
open_bankingConnect bank accountsExplicit submit onlyNo
card_setupSet up payment cardExplicit submit onlyNo
feature_selectionChoose platform featuresExplicit submit onlyNo

Integration Patterns

Building an Onboarding UI

  1. After account creation, fetch onboarding state with GET /v0/user/me?expand=onboarding
  2. Find the step with "status": "current" and render that step’s UI
  3. When the user completes the step’s action, call POST /v0/user/me/onboarding/submit
  4. The response contains the updated state — re-render based on the new current step
  5. Repeat until is_complete is true
Pseudocode:
state = GET /v0/user/me?expand=onboarding

while not state.onboarding.is_complete:
    current = find step where status == "current"
    render_step_ui(current.step)
    await user_action()
    state = POST /v0/user/me/onboarding/submit { step: current.step }

show_onboarding_complete()
Progress bar: Calculate progress from the steps array:
total = len(steps)
done = count(steps where status in ["completed", "skipped"])
progress = done / total

Handling Configurable Steps

Steps with "gated": true may have "status": "skipped" if the platform has disabled them. When building a step list UI:
  • Filter the steps array to exclude skipped steps, or show them as completed
  • Do not hardcode which steps exist — always derive from the steps array
  • The order in the array is the canonical step order
active_steps = [s for s in steps if s.status != "skipped"]

Implicit Step Advancement

Two steps auto-advance without requiring an explicit submit call: Phone verification: When the user successfully verifies their phone via PUT /v0/user/me/phone/code, the phone_verification onboarding step is automatically submitted. After calling the phone verification endpoint, re-fetch onboarding state to see the updated step. KYC verification: When checking KYC status via GET /v0/kyc/status returns an approved result and the user is currently on the kyc_verification step, it is automatically submitted. After initiating KYC, poll the status endpoint and re-fetch onboarding state once approved. In both cases, the client should re-fetch state after the triggering action to see the advancement:
# Phone verification auto-advances onboarding
curl -X PUT /v0/user/me/phone/code \
  -H "x-juno-jwt: <token>" \
  -d '{"verification_code": "123456"}'

# Re-fetch to see updated onboarding state
curl /v0/user/me?expand=onboarding \
  -H "x-juno-jwt: <token>"

Error Handling

All error responses follow RFC 7807 Problem Details format:
{
  "type": "https://api.sumvin.com/errors/usr-404-001",
  "title": "User Not Found",
  "status": 404,
  "detail": "User not found",
  "instance": "/v0/user/me/onboarding/submit",
  "error_code": "USR-404-001"
}
StatusMeaning
401 UnauthorizedMissing or invalid authentication token
404 Not FoundUser account does not exist (create account first)
422 Unprocessable ContentRequest body missing required step field

Reference Tables

Step Identifiers

ValueDescription
createdInitial state after account creation (not a user-facing step)
phone_verificationPhone number verification via SMS
kyc_verificationIdentity verification (KYC)
open_bankingBank account connection
card_setupPayment card setup
feature_selectionPlatform feature selection
completeTerminal state — onboarding finished

Event Types

ValueDescription
step_enteredUser arrived at a new step
step_submittedUser submitted a step (recorded by submit endpoint, not advance)
step_completedStep was completed and user advanced past it
step_skippedStep was skipped by platform configuration

Status Values

ValueDescription
pendingStep not yet reached
currentUser is on this step — action required
submittedStep was submitted (transient — advances immediately)
completedStep finished
skippedStep disabled by platform configuration