Convenções da API
As regras que todo endpoint segue. URL base, versionamento, erros, paginação, idempotência, limites de requisições e formatos de dados.
Tudo nesta página vale para todos os endpoints da Admin API e da Storefront API. As páginas de referência documentam apenas o que é específico de cada operação.
URL base e versionamento
https://api.mobiari.com/v1| API | Caminhos | Quem chama | Documento OpenAPI |
|---|---|---|---|
| Admin | /v1/... | O painel e os apps móveis, suas integrações | admin.json |
| Storefront | /v1/storefront-api/... | Temas de loja virtual, navegadores dos compradores | storefront.json |
Aponte o Postman, um gerador de SDK ou um servidor de mock para os
documentos OpenAPI; a entrada servers deles já inclui /v1.
Dentro de /v1, as mudanças são apenas aditivas. Podemos, sem aviso
prévio:
- adicionar endpoints, campos opcionais de requisição e parâmetros de query;
- adicionar campos às respostas;
- adicionar valores a enums de resposta;
- adicionar
codes de erro para novos casos de falha.
Qualquer outra mudança é incompatível (breaking change) e sai em uma nova versão ou passa por um ciclo de descontinuação: remover ou renomear um caminho, campo ou código de erro, mudar um tipo, tornar obrigatório um campo opcional ou mudar o significado de um código de status. Todas as mudanças estão no Changelog da API.
Escreva clientes que resistam a mudanças aditivas:
- ignore campos de resposta que você não conhece;
- trate um valor de enum desconhecido como "outro", nunca como erro;
- tome decisões com base em
code, não na mensagem de erro.
Endpoints documentados
Um endpoint passa a ser coberto por este contrato quando aparece na
referência da API. Áreas ainda em documentação já respondem em /v1 com o
mesmo formato de erro, idempotência e limites de requisições (rate limits), mas algumas
respostas mantêm formatos antigos (como listas sem o envelope Page) até
entrarem na referência.
Autenticação
Envie um bearer token em toda requisição que precisar de um:
Authorization: Bearer <token>O token é um token de acesso de sessão obtido em POST /v1/auth/login
(de curta duração, renovado com um refresh token) ou um token de API da
loja (mobi_st_…) que você cria na loja para um script ou integração. Os dois
podem fazer no máximo o que o usuário por trás deles pode. Veja
Autenticação.
A maioria das operações da Storefront API é pública; cada operação na referência lista os próprios requisitos.
Erros
Toda resposta de erro, em todos os endpoints, tem um único formato:
{
"error": "Cannot merge a customer into itself",
"code": "same_customer",
"details": { "...": "optional, depends on code" }
}| Campo | Significado |
|---|---|
code | Estável, em snake_case. Tome decisões com base nele. Renomear um código é uma mudança incompatível. |
error | Mensagem legível por humanos. O texto pode mudar a qualquer momento; exiba, não interprete. |
details | Contexto estruturado opcional. O formato é fixo para cada code. |
Quando nenhum código específico se aplica, code é derivado do status. Os
códigos mais comuns:
| Status | code | Quando |
|---|---|---|
| 400 | bad_request | Requisição malformada. |
| 400 | invalid_cursor, invalid_limit, invalid_offset | Parâmetros de paginação inválidos. |
| 401 | unauthorized | Credenciais ausentes, expiradas ou revogadas. |
| 401 | invalid_refresh_token, refresh_token_reused | A renovação falhou; faça login de novo. |
| 403 | forbidden, missing_permission | Credenciais válidas, mas o usuário ou o escopo do token não tem a permissão. |
| 404 | not_found | O recurso não existe ou não pertence a esta loja. |
| 404 | route_not_found | Não existe esse endpoint em /v1. Confira o caminho e o método. |
| 409 | conflict | O estado do recurso não permite esta operação. |
| 409 | idempotency_request_in_progress | Veja Idempotência. |
| 413 | payload_too_large | Corpo acima do limite do endpoint. |
| 422 | validation_failed | Erros de campo em details.fields. |
| 422 | idempotency_key_reused | Veja Idempotência. |
| 429 | rate_limited | Veja Limites de requisições. |
| 500 | internal_error | Falha nossa. É seguro repetir requisições idempotentes. |
Na referência, cada operação lista os códigos específicos que pode retornar.
Uma falha de validação indica cada campo com problema:
{
"error": "Invalid phone",
"code": "validation_failed",
"details": { "fields": { "phone": "at most 20 digits" } }
}Respostas com falha trazem um header x-trace-id. Inclua esse valor ao
relatar um problema; ele aponta direto para a requisição nos nossos logs.
Paginação
Todo endpoint que retorna uma coleção retorna um Page:
{
"data": [{ "id": "…" }, { "id": "…" }],
"has_more": true,
"next_cursor": "bzo1MA",
"total": 812
}| Campo | Significado |
|---|---|
data | Os itens desta página, na ordem documentada pelo endpoint. |
has_more | Se existe uma próxima página. |
next_cursor | Envie como cursor para obter a próxima página. null na última página. |
total | Quantidade total de itens correspondentes. Presente apenas com include_total=true. |
| Parâmetro de query | Significado |
|---|---|
limit | Tamanho da página, de 1 a 200. Padrão: 50. |
cursor | next_cursor da página anterior. Omita na primeira página. |
offset | Itens a pular, para interfaces que saltam direto para a página N. Ignorado quando cursor é informado. |
include_total | true para receber total. Custa uma consulta extra; deixe desligado quando só for percorrer as páginas. |
Cursores são strings opacas: armazene e devolva exatamente como vieram,
nunca monte nem decodifique cursores. Para ler uma lista inteira, siga
next_cursor até has_more ser false. A paginação por cursor continua
correta quando linhas são adicionadas enquanto você percorre a lista; com
offsets, linhas podem ser puladas ou repetidas.
Listas limitadas que pertencem a um recurso (os itens de um pedido, os endereços de um cliente) são arrays simples dentro desse recurso, não páginas.
Percorrendo todas as páginas com curl
url="https://api.mobiari.com/v1/stores/$STORE_ID/customers/duplicates?limit=200"
cursor=""
while :; do
page=$(curl -sf "$url${cursor:+&cursor=$cursor}" -H "Authorization: Bearer $BP_TOKEN")
echo "$page" | jq -c '.data[]'
cursor=$(echo "$page" | jq -r '.next_cursor // empty')
[ -z "$cursor" ] && break
donePercorrendo todas as páginas em TypeScript
type Page<T> = {
data: T[];
has_more: boolean;
next_cursor: string | null;
total?: number;
};
const BASE = 'https://api.mobiari.com/v1';
async function* listAll<T>(path: string, token: string, query: Record<string, string> = {}) {
let cursor: string | null = null;
do {
const url = new URL(BASE + path);
for (const [k, v] of Object.entries(query)) url.searchParams.set(k, v);
url.searchParams.set('limit', '200');
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
if (!res.ok) {
const err = await res.json();
throw new Error(`${res.status} ${err.code}: ${err.error}`);
}
const page: Page<T> = await res.json();
yield* page.data;
cursor = page.has_more ? page.next_cursor : null;
} while (cursor);
}
for await (const group of listAll(`/stores/${storeId}/customers/duplicates`, token)) {
console.log(group);
}Idempotência
Um POST que excede o tempo limite pode ou não ter sido processado. Envie um
header Idempotency-Key e repita com a mesma chave para receber a
resposta original em vez de um segundo pedido, reembolso ou mensagem:
curl -X POST "https://api.mobiari.com/v1/stores/$STORE_ID/customers/$CUSTOMER_ID/merge" \
-H "Authorization: Bearer $BP_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5f0c1c52-2a3e-4d8e-9d59-0d3c2f6f4b1e" \
-d '{"from_customer_id": "…"}'- Use um valor aleatório novo (um UUID) para cada operação lógica, com no máximo 255 caracteres. As chaves são isoladas por credencial.
- Uma nova tentativa com a mesma chave, caminho e corpo dentro de
24 horas retorna o status e o corpo armazenados, com
Idempotent-Replayed: true. - Uma nova tentativa enquanto a primeira requisição ainda está em execução
recebe 409
idempotency_request_in_progress. Aguarde um pouco e tente de novo. - A mesma chave com corpo ou caminho diferente recebe 422
idempotency_key_reused. Isso é um bug no cliente: uma chave, uma requisição. - Respostas 5xx não são armazenadas, então uma nova tentativa após um erro de servidor executa a requisição de novo.
- Vale para requisições POST com corpo JSON (ou vazio) de até 1 MB. Uploads de arquivo ignoram o header. Outros métodos não precisam dele: GET, PUT e DELETE já são seguros para repetir.
Limites de requisições
Cada chamador tem uma cota de requisições (rate limit) por janela de um minuto: por token quando autenticado, por endereço do cliente caso contrário.
| Chamador | Cota padrão |
|---|---|
| Autenticado | 1200 requisições / minuto |
| Anônimo | 300 requisições / minuto |
Toda resposta informa a sua situação:
RateLimit-Limit: 1200
RateLimit-Remaining: 1187
RateLimit-Reset: 42RateLimit-Reset é o número de segundos até a janela reiniciar. Acima da
cota, você recebe 429 rate_limited com Retry-After (em segundos) e
details.retry_after_seconds. Aguarde esse tempo e tente de novo:
async function call(url: string, init: RequestInit): Promise<Response> {
for (;;) {
const res = await fetch(url, init);
if (res.status !== 429) return res;
const wait = Number(res.headers.get('Retry-After') ?? '1');
await new Promise((r) => setTimeout(r, wait * 1000));
}
}As cotas podem mudar; leia os headers em vez de fixar os números no código.
Descontinuação
A API também responde nos caminhos antigos sem versão (/stores/... em vez
de /v1/stores/...) para clientes criados antes do /v1. Essas respostas
avisam isso:
Deprecation: @1790812800
Link: </v1/stores/…/orders>; rel="successor-version"
Sunset: Sat, 31 Jan 2027 00:00:00 GMTDeprecation(RFC 9745): a data em que o caminho foi descontinuado, como timestamp Unix (01/10/2026).Linkcomrel="successor-version": o caminho/v1a usar no lugar.Sunset(RFC 8594): quando o caminho antigo deixa de funcionar. Enviado assim que uma data é definida.
Os mesmos headers marcam qualquer coisa descontinuada dentro de /v1 no
futuro. Registre esses headers nos logs do seu cliente para saber de um
sunset antes que ele aconteça.
Formatos de dados
| Tipo | Convenção | Exemplo |
|---|---|---|
| Valores monetários | Centavos inteiros, em campos com nome *_cents. Nunca floats. | "total_cents": 18900 é R$ 189,00 |
| Timestamps | RFC 3339 em UTC, em campos com nome *_at | "created_at": "2026-10-01T14:03:22.418Z" |
| IDs | UUIDs, como strings | "id": "0199a4f2-6c1e-7d3a-9b1f-3e2c8a5d7f10" |
| Enums | Strings em snake_case. Novos valores podem aparecer; trate os desconhecidos. | "status": "paid" |
| Corpos de requisição | JSON, Content-Type: application/json, exceto uploads de arquivo (multipart) |
Envie timestamps com offset ou Z; a API responde em UTC. Trate IDs como
strings opacas: não os interprete nem dependa da ordenação deles.