Autenticação
Tokens de sessão para apps, tokens de acesso pessoal para scripts e um único modelo de permissões por trás dos dois.
Toda requisição autenticada leva um bearer token:
Authorization: Bearer <token>A API aceita dois tipos de token, de forma intercambiável, em todos os endpoints:
| Tipo | Onde obter | Validade | Para que usar |
|---|---|---|---|
| Token de sessão | POST /v1/auth/login | expires_in (um dia por padrão); renovado com um refresh token | Apps em que uma pessoa faz login: o painel, apps móveis, sua própria interface |
Token de acesso pessoal (bod_…) | POST /v1/stores/{store_id}/api-tokens | Até ser revogado ou até a data de expiração opcional | Scripts, integrações, automações |
Em qualquer caso, o token nunca pode fazer mais do que o usuário a quem ele pertence.
Login
curl -X POST https://api.mobiari.com/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "ana@example.com", "password": "…", "device_name": "Stock sync laptop"}'device_name é opcional; ele identifica a sessão na lista de sessões do
usuário. A resposta é diferenciada pelo campo status:
{
"status": "authenticated",
"token": "eyJhbGciOiJIUzI1NiJ9…",
"token_type": "Bearer",
"expires_in": 86400,
"refresh_token": "bpr_2f8K…",
"refresh_token_expires_in": 2592000,
"session_id": "0199a4f2-6c1e-7d3a-9b1f-3e2c8a5d7f10",
"user": { "id": "…", "email": "ana@example.com", "...": "…" }
}| Campo | Significado |
|---|---|
token | O token de acesso. Envie como Authorization: Bearer <token>. |
expires_in | Segundos até token expirar. Leia este valor; não fixe uma validade no código. |
refresh_token | Troca-se por um novo par em POST /v1/auth/refresh. Começa com bpr_. Funciona uma única vez. |
refresh_token_expires_in | Segundos até o refresh token expirar, caso não seja usado. |
session_id | Este login, como aparece em GET /v1/me/sessions. |
Autenticação em dois fatores
Se a conta tiver um segundo fator ativado, a etapa da senha responde com um desafio em vez de tokens:
{
"status": "two_factor_required",
"challenge": "eyJ…",
"methods": ["totp", "whatsapp"],
"expires_in": 300
}Conclua em até expires_in segundos com um dos methods:
| Método | Chamadas |
|---|---|
totp | POST /v1/auth/2fa/totp/verify com challenge e totp_code (do app autenticador) |
whatsapp | POST /v1/auth/otp/send com challenge, depois POST /v1/auth/otp/verify com challenge e code |
As duas chamadas de verificação retornam a sessão autenticada (token,
refresh_token, …). Novos métodos podem ser adicionados: ignore valores de
methods que você não suporta.
Renovar uma sessão
Antes de token expirar, ou quando uma chamada retornar 401 unauthorized,
troque o refresh token por um novo par:
curl -X POST https://api.mobiari.com/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token": "bpr_2f8K…"}'A resposta é uma nova sessão: os mesmos campos de um login bem-sucedido,
sem status.
Refresh tokens são rotativos. Cada um funciona uma única vez: a resposta
traz um novo refresh_token, e você deve armazená-lo no lugar do antigo.
Reutilizar revoga a sessão. Apresentar um refresh token que já foi usado
é tratado como roubo: a sessão inteira (todos os tokens derivados daquele
login) é revogada e a chamada falha com 401 refresh_token_reused. O usuário
precisa fazer login de novo.
| Erro | Significado | O que fazer |
|---|---|---|
401 invalid_refresh_token | Refresh token desconhecido ou expirado | Faça login de novo |
401 refresh_token_reused | Já usado ou revogado; a sessão agora está encerrada em todos os dispositivos que a usavam | Faça login de novo e corrija o cliente, se não foi um ataque |
Renove a partir de um único lugar por vez
Duas abas ou threads renovando com o mesmo token ao mesmo tempo é
indistinguível de um token roubado: a segunda chamada recebe
refresh_token_reused e desconecta o usuário. Centralize as renovações em
uma única promise em andamento (ou um lock) e faça todas as requisições em
espera usarem o resultado dela.
let refreshing: Promise<Session> | null = null;
function refresh(session: Session): Promise<Session> {
refreshing ??= fetch('https://api.mobiari.com/v1/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: session.refresh_token }),
})
.then(async (res) => {
if (!res.ok) throw new SignedOut((await res.json()).code);
return saveSession(await res.json()); // persist the NEW refresh_token
})
.finally(() => {
refreshing = null;
});
return refreshing;
}Logout e sessões
curl -X POST https://api.mobiari.com/v1/auth/logout \
-H "Content-Type: application/json" \
-d '{"refresh_token": "bpr_2f8K…"}'O logout revoga a sessão: o refresh token dela e os tokens de acesso emitidos para ela deixam de funcionar. A resposta é 204 mesmo para um token desconhecido ou já revogado, então é seguro repetir a chamada.
| Método | Caminho | Finalidade |
|---|---|---|
GET | /v1/me/sessions | Os dispositivos conectados do usuário (um Page), do uso mais recente para o mais antigo, com device_name, last_used_at e current. |
DELETE | /v1/me/sessions/{session_id} | Desconecta um dispositivo. |
Tokens de acesso pessoal não são sessões e não aparecem nessa lista.
Tokens de acesso pessoal
Crie um no painel em Tokens de API, ou pela API:
curl -X POST "https://api.mobiari.com/v1/stores/$STORE_ID/api-tokens" \
-H "Authorization: Bearer $BP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Stock sync script",
"scopes": ["products.read", "orders.read"],
"expires_at": null
}'A resposta inclui o token bruto uma única vez. O servidor guarda apenas
um hash sha256; se você perder o valor, rotacione o token.
bod_<43 characters, URL-safe base64>Mantenha o prefixo bod_: é ele que permite que scanners de segredos (do
GitHub e outros) reconheçam um token vazado.
Como um token se relaciona com o usuário
Um token é sempre emitido em nome de um usuário real, e nunca pode fazer mais do que esse usuário pode. Se o usuário for removido da loja, todos os tokens que ele emitiu deixam de funcionar. Não existem tokens órfãos.
- Atribuição na auditoria. Alterações feitas com um token são registradas em nome do usuário que o emitiu, junto com o nome do token ("Aline, via o terminal do PDV").
- Tokens restritos. Para uma integração que precisa continuar limitada mesmo que você seja o dono, emita o token apenas com os escopos necessários, ou convide um membro dedicado de "integrações" com permissões limitadas e emita o token em nome dele.
Escopos
scopes são códigos de permissão, os mesmos da página de permissões da
equipe (products.read, orders.refund, …); GET /v1/permissions/catalog
lista todos eles. ["*"] significa tudo o que o usuário emissor pode fazer.
Quando chega uma requisição:
effective permissions = the user's permissions ∩ the token's scopesA restrição também se aplica ao dono da loja: um token do dono com apenas
products.read é de fato somente leitura. Você não pode conceder a um token
uma permissão que você mesmo não tem.
Rotacionar e revogar
| Ação | Endpoint | Efeito |
|---|---|---|
| Listar | GET /v1/stores/{store_id}/api-tokens | Nome, escopos, últimos 4 caracteres, last_used_at; nunca o valor bruto |
| Rotacionar | POST /v1/stores/{store_id}/api-tokens/{token_id}/rotate | Mesmo token, novo valor; o valor antigo para de funcionar imediatamente. Use após um vazamento. |
| Revogar | DELETE /v1/stores/{store_id}/api-tokens/{token_id} | Removido definitivamente. 204 No Content. |
curl -X DELETE \
"https://api.mobiari.com/v1/stores/$STORE_ID/api-tokens/$TOKEN_ID" \
-H "Authorization: Bearer $BP_TOKEN"Requisições com um token revogado falham com 401 imediatamente; não há período de carência.
401 ou 403?
| Resposta | Causa | Solução |
|---|---|---|
401 unauthorized | Header ausente, token de sessão expirado, token revogado ou digitado errado | Renove a sessão ou confira o token (o prefixo bod_ foi removido?) |
401 invalid_refresh_token / refresh_token_reused | A renovação falhou | Faça login de novo |
403 forbidden / missing_permission | O token é válido, mas o usuário ou os escopos do token não têm a permissão | Conceda a permissão ou emita um token com esse escopo |
Os erros seguem o formato descrito em Convenções da API.