Autenticação do participante
Sem senha. O mesmo par de endpoints atende quem nunca usou o app e quem já tem conta — a API decide internamente se cria ou apenas autentica.
| Endpoint | O que faz |
|---|---|
POST /v1/auth/wallet/otp/request | Envia o código por SMS. Exige challengeToken do Cloudflare Turnstile. Resposta sempre neutra — não revela se o telefone já tinha conta. |
POST /v1/auth/wallet/otp/verify | Confirma o código. Cria a identidade se o telefone é novo, ou autentica a existente. Devolve accessToken/refreshToken/profile no corpo e seta os cookies wallet_user_*. |
POST /v1/auth/wallet/refresh | Rotaciona o refresh token (corpo, header X-Refresh-Token ou cookie). Rejeita refresh token de outro perfil. |
POST /v1/auth/wallet/logout | Revoga a sessão atual e limpa os três cookies wallet_user_*. |
Exemplos
Os exemplos usam http://127.0.0.1:8787 (a wallet-edge local). Em
produção, troque pela URL pública da Edge do seu ambiente.
- curl
- JavaScript
- Python
curl -X POST http://127.0.0.1:8787/v1/auth/wallet/otp/request \
-H "Content-Type: application/json" \
-d '{
"phone": "+5511999999999",
"challengeToken": "<token do Turnstile>"
}'
const response = await fetch("http://127.0.0.1:8787/v1/auth/wallet/otp/request", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
phone: "+5511999999999",
challengeToken: "<token do Turnstile>",
}),
});
import requests
requests.post(
"http://127.0.0.1:8787/v1/auth/wallet/otp/request",
json={
"phone": "+5511999999999",
"challengeToken": "<token do Turnstile>",
},
)
Depois que o SMS chega, confirme o código:
- curl
- JavaScript
- Python
curl -X POST http://127.0.0.1:8787/v1/auth/wallet/otp/verify \
-H "Content-Type: application/json" \
-d '{
"phone": "+5511999999999",
"token": "123456"
}'
const response = await fetch("http://127.0.0.1:8787/v1/auth/wallet/otp/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ phone: "+5511999999999", token: "123456" }),
});
const session = await response.json();
// session.accessToken, session.refreshToken, session.profile
import requests
response = requests.post(
"http://127.0.0.1:8787/v1/auth/wallet/otp/verify",
json={"phone": "+5511999999999", "token": "123456"},
)
session = response.json()
# session["accessToken"], session["refreshToken"], session["profile"]
Telefone tem que estar em E.164
phone sempre no formato +5511999999999 (com +, código do país, sem
espaço/traço). O Supabase — que emite o OTP por baixo dos panos — guarda o
telefone sem o + internamente; isso já é tratado no backend, o cliente
só precisa mandar sempre com +.
profileComplete: false — o que fazer
Quando verify responde com profile.profileComplete === false, o telefone
é novo e o perfil ainda não tem nome. Nesse caso, colete o nome e chame:
PATCH /v1/auth/wallet/me
Authorization: Bearer <accessToken>
Content-Type: application/json
{ "displayName": "Maria Silva" }
Veja Perfil para o contrato completo desse endpoint.
Rate limit
otp/request é limitado por IP e por hash do telefone normalizado — um
único número não consegue receber SMS em sequência infinita mesmo trocando
de IP, e um IP não consegue floodar números diferentes sem tropeçar no
limite por IP. otp/verify também tem limite próprio, para dificultar
tentativa de força bruta do código de 6 dígitos.