MobiariDocs

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
APICaminhosQuem chamaDocumento OpenAPI
Admin/v1/...O painel e os apps móveis, suas integraçõesadmin.json
Storefront/v1/storefront-api/...Temas de loja virtual, navegadores dos compradoresstorefront.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" }
}
CampoSignificado
codeEstável, em snake_case. Tome decisões com base nele. Renomear um código é uma mudança incompatível.
errorMensagem legível por humanos. O texto pode mudar a qualquer momento; exiba, não interprete.
detailsContexto 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:

StatuscodeQuando
400bad_requestRequisição malformada.
400invalid_cursor, invalid_limit, invalid_offsetParâmetros de paginação inválidos.
401unauthorizedCredenciais ausentes, expiradas ou revogadas.
401invalid_refresh_token, refresh_token_reusedA renovação falhou; faça login de novo.
403forbidden, missing_permissionCredenciais válidas, mas o usuário ou o escopo do token não tem a permissão.
404not_foundO recurso não existe ou não pertence a esta loja.
404route_not_foundNão existe esse endpoint em /v1. Confira o caminho e o método.
409conflictO estado do recurso não permite esta operação.
409idempotency_request_in_progressVeja Idempotência.
413payload_too_largeCorpo acima do limite do endpoint.
422validation_failedErros de campo em details.fields.
422idempotency_key_reusedVeja Idempotência.
429rate_limitedVeja Limites de requisições.
500internal_errorFalha 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
}
CampoSignificado
dataOs itens desta página, na ordem documentada pelo endpoint.
has_moreSe existe uma próxima página.
next_cursorEnvie como cursor para obter a próxima página. null na última página.
totalQuantidade total de itens correspondentes. Presente apenas com include_total=true.
Parâmetro de querySignificado
limitTamanho da página, de 1 a 200. Padrão: 50.
cursornext_cursor da página anterior. Omita na primeira página.
offsetItens a pular, para interfaces que saltam direto para a página N. Ignorado quando cursor é informado.
include_totaltrue 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
done

Percorrendo 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.

ChamadorCota padrão
Autenticado1200 requisições / minuto
Anônimo300 requisições / minuto

Toda resposta informa a sua situação:

RateLimit-Limit: 1200
RateLimit-Remaining: 1187
RateLimit-Reset: 42

RateLimit-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 GMT
  • Deprecation (RFC 9745): a data em que o caminho foi descontinuado, como timestamp Unix (01/10/2026).
  • Link com rel="successor-version": o caminho /v1 a 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

TipoConvençãoExemplo
Valores monetáriosCentavos inteiros, em campos com nome *_cents. Nunca floats."total_cents": 18900 é R$ 189,00
TimestampsRFC 3339 em UTC, em campos com nome *_at"created_at": "2026-10-01T14:03:22.418Z"
IDsUUIDs, como strings"id": "0199a4f2-6c1e-7d3a-9b1f-3e2c8a5d7f10"
EnumsStrings em snake_case. Novos valores podem aparecer; trate os desconhecidos."status": "paid"
Corpos de requisiçãoJSON, 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.

Nesta página