Onboarding with OTP
Use this guide to onboard a new user and obtain a session for profile, team, KYC, and wallet calls.
Prerequisites#
- Staging or production API key from the dashboard
- User email that has not already been onboarded (for create)
- Optional referral code in the form
XXXX####(4 characters + 4 digits)
Flow overview#
- Optional: validate referral code
POST /v1/auth/onboardfor new usersPOST /v1/auth/send-otpPOST /v1/auth/verify-otp→ session + selected profile/team contextGET /v1/auth/refresh-sessionwhen the session nears expiry
1. Validate a referral code (optional)#
curl -sS "https://api-stg.vudy.app/v1/auth/referral/validate?referralCode=ABCD1234" \
-H "x-api-key: vudy_sandbox_YOUR_KEY"const res = await fetch(
"https://api-stg.vudy.app/v1/auth/referral/validate?referralCode=ABCD1234",
{ headers: { "x-api-key": process.env.VUDY_API_KEY } },
);
const { data } = await res.json();
// { exists: true | false }
2. Onboard a new user#
Auth: API key only. Returns 201.
curl -sS https://api-stg.vudy.app/v1/auth/onboard \
-H "x-api-key: vudy_sandbox_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "alex@example.com",
"firstName": "Alex",
"lastName": "Rivera",
"username": "alex-rivera",
"country": "US",
"language": "en",
"isBusiness": false,
"termsOfService": true
}'await fetch("https://api-stg.vudy.app/v1/auth/onboard", {
method: "POST",
headers: {
"x-api-key": process.env.VUDY_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "alex@example.com",
firstName: "Alex",
lastName: "Rivera",
username: "alex-rivera", // min 4; a-z, 0-9, -, _; not api_*
country: "US",
language: "en",
isBusiness: false,
// isBusiness: true requires businessName
termsOfService: true,
}),
});Emails with + sub-addressing are rejected for typical integrator accounts.
Success data (201) matches the auth reference shape:
{
"team": { "id": "team-uuid" },
"profile": {
"id": "profile-uuid",
"nickname": "alex-rivera",
"suborgId": "opaque-organization-id"
},
"teamMember": {
"profileId": "profile-uuid",
"teamId": "team-uuid",
"role": "admin"
},
"invitationAccepted": false,
"walletCreated": true
}Treat suborgId as opaque. walletCreated is false when the onboard country does not create a platform wallet (for example SV). If a pending invitation exists for the email, onboarding joins that team (invitationAccepted: true) instead of creating another one.
3. Send OTP#
curl -sS https://api-stg.vudy.app/v1/auth/send-otp \
-H "x-api-key: vudy_sandbox_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"alex@example.com","language":"en"}'const send = await fetch("https://api-stg.vudy.app/v1/auth/send-otp", {
method: "POST",
headers: {
"x-api-key": process.env.VUDY_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "alex@example.com",
language: "en",
// userType: "individual" | "business" | "admin" // optional filter
}),
}).then((r) => r.json());
const { otpId, identifier, profiles } = send.data;If GET /v1/maintenance returns maintenance, expect 503.
4. Verify OTP and create a session#
Pass profileId when the user has more than one profile.
curl -sS https://api-stg.vudy.app/v1/auth/verify-otp \
-H "x-api-key: vudy_sandbox_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "alex@example.com",
"otpId": "OTP_ID",
"otp": "123456789",
"profileId": "PROFILE_UUID"
}'const verify = await fetch("https://api-stg.vudy.app/v1/auth/verify-otp", {
method: "POST",
headers: {
"x-api-key": process.env.VUDY_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "alex@example.com",
otpId,
otp: "123456789", // min 9 characters
identifier,
profileId: profiles[0].id, // required when multiple profiles
}),
}).then((r) => r.json());
let session = verify.data.session;
const { teams, userType } = verify.data;The session is bound to the selected profile and team. Calls using that session do not need x-profile-id or x-team-id, and those headers do not switch the session actor. Re-run OTP verification with another profileId to change context. See Session and context.
5. Refresh the session#
Auth: API key + session.
curl -sS https://api-stg.vudy.app/v1/auth/refresh-session \
-H "x-api-key: vudy_sandbox_YOUR_KEY" \
-H "Authorization: Bearer SESSION_JWT"The response can contain a replacement data.session. Store and use it for subsequent calls:
const refreshed = await fetch(
"https://api-stg.vudy.app/v1/auth/refresh-session",
{
headers: {
"x-api-key": process.env.VUDY_API_KEY,
Authorization: `Bearer ${session}`,
},
},
).then((r) => r.json());
session = refreshed.data.session ?? session;Status / next steps#
| After | Next |
|---|---|
| Onboard + session | Profiles and teams |
| Before payments | KYC and KYB, Wallets |
| Endpoint fields | Auth reference |