Auth
URL Base: https://channel.ozzieapp.com/v1
Os endpoints de Auth autenticam usuários finais por meio de um código OTP de 6 dígitos entregue via WhatsApp ou e-mail. Isso é distinto da autenticação de cliente de API (que usa um token Bearer estático) — o OTP é para o usuário humano dentro do seu aplicativo.
Para uma visão conceitual de como o OTP se encaixa no fluxo de onboarding completo, veja Arquitetura.
Detalhes do OTP
| Propriedade | Valor |
|---|---|
| Comprimento do código | 6 dígitos |
| TTL | 10 minutos |
| Uso único | Sim — invalidado na primeira verificação bem-sucedida |
| Armazenamento | Hash bcrypt |
identifier | Telefone E.164 (ex.: +5511999990000) ou endereço de e-mail |
Detecção automática de channel | Telefone E.164 → whatsapp; endereço de e-mail → email |
POST /v1/auth/otp/request
Gera um código OTP de 6 dígitos e o entrega ao usuário via WhatsApp ou e-mail.
Corpo da requisição
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
identifier | string | Sim | Telefone no formato E.164 ou endereço de e-mail |
channel | string | Não | "whatsapp" ou "email". Detectado automaticamente a partir do identifier se omitido. |
language | string | Não | "pt" | "en" | "es" — idioma da mensagem OTP. Padrão "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);
Resposta (200 OK):
{
"object": "otp_request",
"data": {
"channel": "whatsapp",
"masked": "+55119****0000",
"expires_at": "2026-05-16T14:40:00Z"
}
}
| Campo | Descrição |
|---|---|
channel | Canal de entrega utilizado: "whatsapp" ou "email" |
masked | Versão mascarada do identificador — seguro para exibir ao usuário como confirmação |
expires_at | Timestamp ISO 8601 de quando o código expira (10 minutos após a emissão) |
POST /v1/auth/otp/verify
Verifica o código OTP digitado pelo usuário. Em caso de sucesso, retorna o user_id do Ozzie e o onboarding_stage atual do usuário.
Corpo da requisição
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
identifier | string | Sim | O mesmo telefone ou e-mail usado na etapa de requisição |
code | string | Sim | Código de 6 dígitos que o usuário recebeu |
- 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"
Resposta (200 OK):
{
"object": "otp_verify",
"data": {
"user_id": "external:usr_8821",
"core_user_id": "ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE",
"onboarding_stage": "financial_intake"
}
}
| Campo | Descrição |
|---|---|
user_id | O identificador de usuário do Ozzie no formato external:{id} — use-o nos parâmetros de caminho subsequentes |
core_user_id | O UUID interno do Ozzie |
onboarding_stage | Estágio atual: financial_intake | personality | plan | active |
Erros
| Código | HTTP | Quando |
|---|---|---|
INVALID_CODE | 400 | O código não corresponde ou já foi utilizado |
CODE_EXPIRED | 400 | O código foi emitido há mais de 10 minutos |
NOT_FOUND | 404 | Nenhum OTP pendente encontrado para este identificador |
VALIDATION_ERROR | 400 | O campo identifier ou code está ausente ou malformado |
Fluxo de onboarding completo
Após uma verificação bem-sucedida, use onboarding_stage para redirecionar o usuário para a tela correta:
onboarding_stage | Próxima etapa |
|---|---|
financial_intake | Exibir o formulário de intake financeiro → POST /v1/users/:id/financial-intake |
personality | Exibir o quiz de personalidade → GET /v1/personality/questions + POST /v1/personality/score |
plan | Exibir a tela de geração de plano → POST /v1/users/:id/plan/generate, depois PATCH /v1/users/:id/onboarding/complete |
active | Usuário totalmente integrado — ir diretamente para a tela principal de chat |
SESSION_SECRET e o gerenciamento de cookies de sessão são responsabilidade do seu app. O Ozzie não emite tokens de sessão — ele apenas valida o código OTP e retorna os dados que seu app precisa para inicializar sua própria sessão.