Auth
URL Base: https://channel.ozzieapp.com/v1
Los endpoints de Auth autentican a los usuarios finales mediante un código OTP de 6 dígitos entregado por WhatsApp o correo electrónico. Esto es distinto de la autenticación del cliente de API (que usa un token Bearer estático) — el OTP es para el usuario humano dentro de tu aplicación.
Para una descripción conceptual de cómo el OTP encaja en el flujo completo de onboarding, consulta Arquitectura.
Detalles del OTP
| Propiedad | Valor |
|---|---|
| Longitud del código | 6 dígitos |
| TTL | 10 minutos |
| Uso único | Sí — invalidado al primer verificado exitoso |
| Almacenamiento | Hasheado con bcrypt |
identifier | Teléfono E.164 (ej. +5511999990000) o dirección de correo electrónico |
Detección automática de channel | Teléfono E.164 → whatsapp; dirección de correo → email |
POST /v1/auth/otp/request
Genera un código OTP de 6 dígitos y lo entrega al usuario por WhatsApp o correo electrónico.
Cuerpo de la solicitud
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
identifier | string | Sí | Teléfono en formato E.164 o dirección de correo electrónico |
channel | string | No | "whatsapp" o "email". Detectado automáticamente desde identifier si se omite. |
language | string | No | "pt" | "en" | "es" — idioma del mensaje OTP. Por defecto "en". |
- curl
- Node.js
curl -X POST https://channel.ozzieapp.com/v1/auth/otp/request \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"identifier": "+5511999990000",
"language": "pt"
}'
const token = Buffer.from(
`${process.env.OZZIE_CLIENT_ID}:${process.env.OZZIE_CLIENT_SECRET}`
).toString('base64');
const response = await fetch('https://channel.ozzieapp.com/v1/auth/otp/request', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
identifier: '+5511999990000',
language: 'pt',
}),
});
const { data } = await response.json();
console.log(data.masked); // "+55119****0000"
console.log(data.expires_at);
Respuesta (200 OK):
{
"object": "otp_request",
"data": {
"channel": "whatsapp",
"masked": "+55119****0000",
"expires_at": "2026-05-16T14:40:00Z"
}
}
| Campo | Descripción |
|---|---|
channel | Canal de entrega utilizado: "whatsapp" o "email" |
masked | Versión enmascarada del identificador — seguro para mostrar al usuario como confirmación |
expires_at | Timestamp ISO 8601 de cuando expira el código (10 minutos desde su emisión) |
POST /v1/auth/otp/verify
Verifica el código OTP que ingresó el usuario. Si tiene éxito, devuelve el user_id de Ozzie y el onboarding_stage actual del usuario.
Cuerpo de la solicitud
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
identifier | string | Sí | El mismo teléfono o correo usado en el paso de solicitud |
code | string | Sí | Código de 6 dígitos que recibió el usuario |
- curl
- Node.js
curl -X POST https://channel.ozzieapp.com/v1/auth/otp/verify \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"identifier": "+5511999990000",
"code": "482910"
}'
const token = Buffer.from(
`${process.env.OZZIE_CLIENT_ID}:${process.env.OZZIE_CLIENT_SECRET}`
).toString('base64');
const response = await fetch('https://channel.ozzieapp.com/v1/auth/otp/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
identifier: '+5511999990000',
code: '482910',
}),
});
if (!response.ok) {
const { error } = await response.json();
// error.code === 'INVALID_CODE' or 'CODE_EXPIRED'
throw new Error(error.code);
}
const { data } = await response.json();
// data.user_id = "external:usr_8821"
// data.core_user_id = "ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE"
// data.onboarding_stage = "financial_intake"
Respuesta (200 OK):
{
"object": "otp_verify",
"data": {
"user_id": "external:usr_8821",
"core_user_id": "ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE",
"onboarding_stage": "financial_intake"
}
}
| Campo | Descripción |
|---|---|
user_id | El identificador de usuario de Ozzie en formato external:{id} — úsalo en los parámetros de ruta subsiguientes |
core_user_id | El UUID interno de Ozzie |
onboarding_stage | Etapa actual: financial_intake | personality | plan | active |
Errores
| Código | HTTP | Cuándo |
|---|---|---|
INVALID_CODE | 400 | El código no coincide o ya fue utilizado |
CODE_EXPIRED | 400 | El código fue emitido hace más de 10 minutos |
NOT_FOUND | 404 | No se encontró OTP pendiente para este identificador |
VALIDATION_ERROR | 400 | El campo identifier o code está ausente o malformado |
Flujo completo de onboarding
Después de una verificación exitosa, usa onboarding_stage para enrutar al usuario a la pantalla correcta:
onboarding_stage | Siguiente paso |
|---|---|
financial_intake | Mostrar el formulario de intake financiero → POST /v1/users/:id/financial-intake |
personality | Mostrar el quiz de personalidad → GET /v1/personality/questions + POST /v1/personality/score |
plan | Mostrar la pantalla de generación del plan → POST /v1/users/:id/plan/generate, luego PATCH /v1/users/:id/onboarding/complete |
active | El usuario completó el onboarding — ir directamente a la pantalla principal de chat |
SESSION_SECRET y la gestión de cookies de sesión son responsabilidad de tu aplicación. Ozzie no emite tokens de sesión — solo valida el código OTP y devuelve los datos que tu aplicación necesita para inicializar su propia sesión.