Portal do desenvolvedor · API Gototem PDV
v1 llms.txt

API Gototem PDV — guia do desenvolvedor

Este guia é para quem vai integrar um sistema próprio (um aplicativo, um painel, uma planilha automatizada) aos dados de uma conta da Gototem. Ele leva do zero à primeira resposta com dados.

A API é somente leitura e fala JSON. Para testar sem escrever código, baixe a coleção pronta para o Postman e o Insomnia. O contrato completo, em OpenAPI, está em openapi.yaml.

Nesta página

Visão geral

A API entrega, em JSON, os dados de uma conta da Gototem que o dono da conta decidiu liberar: as lojas, os eventos de cada loja e os recebimentos por método de pagamento. Ela é somente leitura: nenhuma rota cria, altera ou apaga dado.

Endereço base de produção: https://api.gototem.com.br.

Não existe ambiente de teste, nem chave de teste. Os exemplos e a coleção apontam para a produção. Todas as rotas só leem: testar não altera dado nenhum. Mas toda chamada feita com a chave conta no limite de uso dela, inclusive as que respondem erro (422, 404 de loja ou de evento, 403, 501).

As respostas são JSON em UTF-8. Todo erro vem em application/problem+json, com um code estável para o seu código e o cabeçalho X-Request-Id para o suporte: veja Erros.

As rotas que já estão no ar e as que vêm com o selo "em breve" (respondem 501, e os parâmetros ainda podem mudar) estão na referência da API, geradas do contrato.

Autenticação

A API não tem usuário e senha. Cada integração usa uma chave, criada pelo dono da conta (o lojista) no painel da Gototem. É ele quem cria, edita e rotaciona chaves; o atendimento da Gototem consegue listar e revogar. Você não consegue criá-la sozinho: peça ao lojista.

A chave nunca vai no código de uma página web, num aplicativo distribuído nem num repositório. Veja Onde a chave deve ficar.

Como obter a chave

O que pedir ao lojista:

  1. Entrar no painel, clicar no menu do usuário (o avatar, no canto superior direito) e abrir Opções da conta. Nessa página, no quadro Integrações, abrir Chaves da API.
  2. Clicar em Nova chave.
  3. Dar um nome que identifique o seu sistema (por exemplo, "Aplicativo de vendas").
  4. Em Permissões, marcar o que o seu sistema precisa: Visualizar recebimentos por método de pagamento (grupo Consultas) e, se for filtrar por evento, Visualizar eventos (grupo Eventos).
  5. Em Estabelecimentos, escolher as lojas que o seu sistema deve enxergar (ou "Todos os estabelecimentos").
  6. Conferir a Validade: toda chave vence. O campo já vem com 12 meses a partir de hoje e aceita até 24 meses. O seu sistema enxerga a validade no ping (campo expires_at): combine com o lojista quem avisa e quando ela será estendida.
  7. Clicar em Criar chave e copiar a chave na hora: ela aparece uma única vez. Na mesma tela, o quadro "O que enviar ao desenvolvedor" traz o link desta documentação.

O dono da conta recebe um e-mail sempre que uma chave é criada ou rotacionada, e também quando uma edição amplia o acesso dela (mais permissões, mais lojas, validade maior ou chave reativada).

A chave começa com gtpk_ e tem 48 caracteres no total.

Não é a tela "Usuários da API". Ela mostra um campo "API Token", que é outra coisa e não funciona nesta API. A chave certa vem da tela "Chaves da API" e começa com gtpk_.

Trate a chave como uma senha: quem a tem faz, nas lojas que ela alcança, tudo o que ela tem permissão para fazer.

Como enviar a chave

Toda chamada leva a chave no cabeçalho Authorization, no esquema Bearer. O endereço da API é HTTPS: nunca envie a chave por HTTP.

HTTP
GET /v1/api-public/ping HTTP/1.1
Host: api.gototem.com.br
Authorization: Bearer <a chave que o lojista copiou>

Não existe outra forma de enviar a chave: ela não vai na query nem no corpo. Os exemplos completos, em cinco linguagens, estão em A primeira chamada.

O que a chave libera

O que a chave libera
Nome da permissão Na tela do lojista Rota
sale-by-payment-method.view Consultas: Visualizar recebimentos por método de pagamento GET /v1/api-public/sales/by-payment-method
event.view Eventos: Visualizar eventos GET /v1/api-public/events

O nome da permissão é o que o ping lista em credential.permissions. ping e places funcionam com qualquer chave válida, sem permissão nenhuma. Rota que a chave não tem permissão para ler responde 403 com code forbidden_permission, e o campo required_permission do erro diz qual permissão falta (por exemplo, event.view): é esse o nome a passar ao lojista, que a marca na chave. Veja O 403 diz qual permissão falta.

As rotas com o selo "em breve" ainda não têm permissão definida. Hoje elas respondem 501 a qualquer chave válida; quando forem ligadas, cada uma vai exigir a permissão dela, que o lojista terá de marcar na chave. Não planeje a integração contando que "qualquer chave serve" nessas rotas.

A primeira chamada

Os exemplos usam duas variáveis de ambiente: o endereço da API e a sua chave. Defina as duas no terminal em que você vai rodar os exemplos:

Variáveis (bash: Linux, macOS, Git Bash)
export GOTOTEM_API="https://api.gototem.com.br"
export GOTOTEM_API_KEY="<a chave que o lojista copiou>"
Variáveis (PowerShell, no Windows)
$env:GOTOTEM_API = "https://api.gototem.com.br"
$env:GOTOTEM_API_KEY = "<a chave que o lojista copiou>"

Os exemplos principais vêm em cinco linguagens: bash (o terminal do Linux, do macOS ou o Git Bash, com o comando curl), PowerShell, Python (com a biblioteca requests), JavaScript (o fetch do Node 18 ou mais novo, num arquivo .mjs) e PHP (com a extensão curl). Escolha a sua numa aba: a escolha vale para todos os exemplos da página e fica guardada neste navegador para a próxima visita (com o JavaScript do navegador desligado, os cinco aparecem um abaixo do outro). Os exemplos em Python, JavaScript e PHP leem as mesmas duas variáveis de ambiente.

A primeira chamada é o ping, que diz a quem a chave pertence:

GET/v1/api-public/pingQualquer chave válidaLimite default

Toda chamada leva a chave no cabeçalho Authorization (bearer em minúsculas e espaços a mais são aceitos):

curl
curl -s "$GOTOTEM_API/v1/api-public/ping" \
  -H "Authorization: Bearer $GOTOTEM_API_KEY"
PowerShell
Invoke-RestMethod -Uri "$env:GOTOTEM_API/v1/api-public/ping" `
  -Headers @{ Authorization = "Bearer $env:GOTOTEM_API_KEY" }
Python (requests)
import os
import requests

API = os.environ["GOTOTEM_API"]
HEADERS = {"Authorization": "Bearer " + os.environ["GOTOTEM_API_KEY"]}

resposta = requests.get(API + "/v1/api-public/ping", headers=HEADERS, timeout=30)
print(resposta.status_code, resposta.json())
JavaScript (Node 18+, arquivo .mjs)
const API = process.env.GOTOTEM_API;
const HEADERS = { Authorization: "Bearer " + process.env.GOTOTEM_API_KEY };

const resposta = await fetch(API + "/v1/api-public/ping", { headers: HEADERS });
console.log(resposta.status, await resposta.json());
PHP (curl)
<?php
$api = getenv('GOTOTEM_API');
$chave = getenv('GOTOTEM_API_KEY');

$ch = curl_init($api . '/v1/api-public/ping');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $chave],
    CURLOPT_TIMEOUT => 30,
]);
$corpo = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);

echo $status, ' ', $corpo, PHP_EOL;

No PowerShell, Invoke-RestMethod já devolve o JSON como objeto. Num Windows antigo, se a chamada falhar com erro de conexão segura, rode antes [Net.ServicePointManager]::SecurityProtocol = 'Tls12'.

Resposta 200
{
  "credential": {
    "id": "Cr3d3nc14lId00000000000000000001",
    "name": "Aplicativo de vendas",
    "permissions": ["sale-by-payment-method.view", "event.view"],
    "expires_at": "2027-10-11T02:59:59Z",
    "superseded": false,
    "superseded_until": null
  },
  "account": { "id": "C0nt4Id0000000000000000000000001", "name": "Restaurante Exemplo" },
  "rate_limit": { "default_per_minute": 60, "heavy_per_minute": 10 },
  "server_time": "2026-10-08T17:51:06Z",
  "api_version": "v1"
}

Se a resposta for 401, a chave está errada ou deixou de valer; quando ela já funcionou, na maioria dos casos o campo reason diz o motivo. expires_at é a validade da chave, e superseded avisa que ela foi trocada. Os motivos, as exceções e esses dois campos estão em Como saber o estado da chave.

rate_limit traz os limites da chave por minuto. Ele vem null quando nenhum limite está sendo aplicado no momento; não conte com isso: planeje as chamadas pelos Limites de uso.

As lojas da chave

GET/v1/api-public/placesQualquer chave válidaLimite default

É a mesma chamada do ping, com outro caminho:

curl
curl -s "$GOTOTEM_API/v1/api-public/places" \
  -H "Authorization: Bearer $GOTOTEM_API_KEY"
PowerShell
$lojas = Invoke-RestMethod -Uri "$env:GOTOTEM_API/v1/api-public/places" `
  -Headers @{ Authorization = "Bearer $env:GOTOTEM_API_KEY" }

$lojas.data | ForEach-Object { "$($_.id) $($_.name) $($_.timezone)" }
Python (requests)
import os
import requests

API = os.environ["GOTOTEM_API"]
HEADERS = {"Authorization": "Bearer " + os.environ["GOTOTEM_API_KEY"]}

resposta = requests.get(API + "/v1/api-public/places", headers=HEADERS, timeout=30)
resposta.raise_for_status()
for loja in resposta.json()["data"]:
    print(loja["id"], loja["name"], loja["timezone"])
JavaScript (Node 18+, arquivo .mjs)
const API = process.env.GOTOTEM_API;
const HEADERS = { Authorization: "Bearer " + process.env.GOTOTEM_API_KEY };

const resposta = await fetch(API + "/v1/api-public/places", { headers: HEADERS });
if (!resposta.ok) throw new Error("HTTP " + resposta.status);
const lojas = await resposta.json();

for (const loja of lojas.data) {
  console.log(loja.id, loja.name, loja.timezone);
}
PHP (curl)
<?php
$api = getenv('GOTOTEM_API');
$chave = getenv('GOTOTEM_API_KEY');

$ch = curl_init($api . '/v1/api-public/places');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $chave],
    CURLOPT_TIMEOUT => 30,
]);
$corpo = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) !== 200) {
    throw new RuntimeException('HTTP ' . curl_getinfo($ch, CURLINFO_HTTP_CODE));
}

foreach (json_decode($corpo, true)['data'] as $loja) {
    echo $loja['id'], ' ', $loja['name'], ' ', $loja['timezone'], PHP_EOL;
}
Resposta 200
{
  "all": false,
  "data": [
    { "id": "L0j4C3ntr0Id00000000000000000000", "name": "Loja Centro", "timezone": "America/Sao_Paulo", "active": true },
    { "id": "L0j4M4n4usId00000000000000000000", "name": "Loja Manaus", "timezone": "America/Manaus", "active": true }
  ]
}

Guarde de cada loja:

  • id: o identificador da loja, usado no parâmetro place;
  • name: o nome para mostrar na sua tela (pode vir null, assim como active);
  • timezone: o fuso da loja, para montar os períodos (veja Datas e fuso).

all: true quer dizer que a chave vale para todas as lojas da conta, inclusive as que forem criadas depois. Esta rota não precisa de permissão especial: qualquer chave válida a lê. Ela não é paginada.

Os eventos de uma loja

GET/v1/api-public/eventsPermissão event.viewLimite default

Para filtrar os recebimentos por um evento, você precisa do id dele. Esta rota lista os eventos de uma loja da chave (ela não alcança lojas fora da chave), do cadastrado mais recentemente para o mais antigo.

Parâmetros: Os eventos de uma loja
Parâmetro Obrigatório O que é
place sim o id de uma loja, vindo de /places
search não parte do nome do evento, sem diferenciar maiúsculas nem acentos (1 a 100 caracteres)
limit não itens por página, de 1 a 100 (padrão 50)
cursor não o next_cursor da página anterior

Sobre o search: sao joao acha "São João", e %, _ e \ são procurados como texto comum (não são curingas). Um caractere de controle ou invisível em qualquer posição (tabulação, quebra de linha, espaço de largura zero) é recusado com 422 e param igual a search.

curl
curl -s "$GOTOTEM_API/v1/api-public/events?place=L0j4C3ntr0Id00000000000000000000&search=rock" \
  -H "Authorization: Bearer $GOTOTEM_API_KEY"
PowerShell
$url = "$env:GOTOTEM_API/v1/api-public/events?place=L0j4C3ntr0Id00000000000000000000&search=rock"
$pagina = Invoke-RestMethod -Uri $url -Headers @{ Authorization = "Bearer $env:GOTOTEM_API_KEY" }

$pagina.data | ForEach-Object { "$($_.id) $($_.name)" }
Python (requests)
import os
import requests

API = os.environ["GOTOTEM_API"]
HEADERS = {"Authorization": "Bearer " + os.environ["GOTOTEM_API_KEY"]}

params = {"place": "L0j4C3ntr0Id00000000000000000000", "search": "rock"}
resposta = requests.get(API + "/v1/api-public/events", headers=HEADERS, params=params, timeout=30)
resposta.raise_for_status()
for evento in resposta.json()["data"]:
    print(evento["id"], evento["name"])
JavaScript (Node 18+, arquivo .mjs)
const API = process.env.GOTOTEM_API;
const HEADERS = { Authorization: "Bearer " + process.env.GOTOTEM_API_KEY };

const params = new URLSearchParams({ place: "L0j4C3ntr0Id00000000000000000000", search: "rock" });
const resposta = await fetch(API + "/v1/api-public/events?" + params, { headers: HEADERS });
if (!resposta.ok) throw new Error("HTTP " + resposta.status);
const pagina = await resposta.json();

for (const evento of pagina.data) {
  console.log(evento.id, evento.name);
}
PHP (curl)
<?php
$api = getenv('GOTOTEM_API');
$chave = getenv('GOTOTEM_API_KEY');

$params = ['place' => 'L0j4C3ntr0Id00000000000000000000', 'search' => 'rock'];
$ch = curl_init($api . '/v1/api-public/events?' . http_build_query($params));
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $chave],
    CURLOPT_TIMEOUT => 30,
]);
$corpo = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) !== 200) {
    throw new RuntimeException('HTTP ' . curl_getinfo($ch, CURLINFO_HTTP_CODE));
}

foreach (json_decode($corpo, true)['data'] as $evento) {
    echo $evento['id'], ' ', $evento['name'], PHP_EOL;
}

Em Python, JavaScript e PHP os exemplos montam a consulta pelos parâmetros (params, URLSearchParams, http_build_query), que já escapam espaço e acento do search. No curl e no PowerShell o endereço vai escrito inteiro: escape o valor você mesmo (search=sao%20joao).

Resposta 200
{
  "place": "L0j4C3ntr0Id00000000000000000000",
  "search": "rock",
  "data": [
    { "id": "Ev3nt0Id000000000000000000000001", "name": "Rock Night", "starts_at": "2026-10-09T23:00:00Z", "ends_at": "2026-10-10T07:00:00Z", "active": true }
  ],
  "next_cursor": null
}
  • starts_at e ends_at vêm em UTC, com Z no final, como toda data da resposta. Para mostrar o horário da loja, converta para o timezone dela: no exemplo, 2026-10-09T23:00:00Z é 20:00 em São Paulo. ends_at vem null quando o evento não tem término válido, e starts_at vem null quando ele não tem data.
  • Eventos desativados também aparecem ("active": false): as vendas de um evento passado continuam consultáveis.
  • next_cursor diferente de null quer dizer que há mais eventos: repita a mesma chamada com cursor=<next_cursor>.

Esta rota pede a permissão Visualizar eventos (event.view).

Para ler todas as páginas, repita a chamada enquanto next_cursor vier preenchido, sem mudar place nem search. O cursor é opaco e pertence à consulta que o gerou:

  • usado com outra loja, ele é recusado com 422, param igual a cursor e o detail "cursor" does not belong to this query.;
  • com outro search, o resultado não é definido;
  • um cursor que não veio desta API, ou cujo evento foi excluído no meio da leitura, responde 422 com o detail "cursor" is not valid.: recomece da primeira página;
  • o cursor não expira com o tempo: ele vale enquanto o último evento da página anterior existir;
  • o limit pode mudar entre páginas: a leitura continua do evento seguinte ao último que você recebeu, sem repetir nem pular nenhum.
curl
# primeira página
curl -s "$GOTOTEM_API/v1/api-public/events?place=L0j4C3ntr0Id00000000000000000000&limit=100" \
  -H "Authorization: Bearer $GOTOTEM_API_KEY"

# próxima página: a mesma chamada, com o next_cursor que veio na resposta
curl -s "$GOTOTEM_API/v1/api-public/events?place=L0j4C3ntr0Id00000000000000000000&limit=100&cursor=<next_cursor>" \
  -H "Authorization: Bearer $GOTOTEM_API_KEY"
PowerShell
$headers = @{ Authorization = "Bearer $env:GOTOTEM_API_KEY" }
$url = "$env:GOTOTEM_API/v1/api-public/events?place=L0j4C3ntr0Id00000000000000000000&limit=100"
$eventos = @()
$cursor = $null

do {
    $pagina = Invoke-RestMethod -Uri $(if ($cursor) { "$url&cursor=$cursor" } else { $url }) -Headers $headers
    $eventos += $pagina.data
    $cursor = $pagina.next_cursor
} while ($cursor)

"$($eventos.Count) eventos"
Python (requests)
import os
import requests

API = os.environ["GOTOTEM_API"]
HEADERS = {"Authorization": "Bearer " + os.environ["GOTOTEM_API_KEY"]}

params = {"place": "L0j4C3ntr0Id00000000000000000000", "limit": 100}
eventos = []

while True:
    resposta = requests.get(API + "/v1/api-public/events", headers=HEADERS, params=params, timeout=30)
    resposta.raise_for_status()
    pagina = resposta.json()
    eventos.extend(pagina["data"])
    if pagina["next_cursor"] is None:
        break
    params["cursor"] = pagina["next_cursor"]

print(len(eventos), "eventos")
JavaScript (Node 18+, arquivo .mjs)
const API = process.env.GOTOTEM_API;
const HEADERS = { Authorization: "Bearer " + process.env.GOTOTEM_API_KEY };

const params = new URLSearchParams({ place: "L0j4C3ntr0Id00000000000000000000", limit: "100" });
const eventos = [];

for (;;) {
  const resposta = await fetch(API + "/v1/api-public/events?" + params, { headers: HEADERS });
  if (!resposta.ok) throw new Error("HTTP " + resposta.status);
  const pagina = await resposta.json();
  eventos.push(...pagina.data);
  if (pagina.next_cursor === null) break;
  params.set("cursor", pagina.next_cursor);
}

console.log(eventos.length, "eventos");
PHP (curl)
<?php
$api = getenv('GOTOTEM_API');
$chave = getenv('GOTOTEM_API_KEY');

$params = ['place' => 'L0j4C3ntr0Id00000000000000000000', 'limit' => 100];
$eventos = [];

do {
    $ch = curl_init($api . '/v1/api-public/events?' . http_build_query($params));
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $chave],
        CURLOPT_TIMEOUT => 30,
    ]);
    $corpo = curl_exec($ch);
    if (curl_getinfo($ch, CURLINFO_HTTP_CODE) !== 200) {
        throw new RuntimeException('HTTP ' . curl_getinfo($ch, CURLINFO_HTTP_CODE));
    }
    $pagina = json_decode($corpo, true);
    $eventos = array_merge($eventos, $pagina['data']);
    $params['cursor'] = $pagina['next_cursor'];
} while ($pagina['next_cursor'] !== null);

echo count($eventos), ' eventos', PHP_EOL;

Contrato e ferramentas

Três arquivos acompanham este guia, publicados ao lado dele: o contrato OpenAPI, a coleção pronta para o Postman e o Insomnia e um resumo para agentes de IA. Nenhum deles precisa de chave.

Contrato OpenAPI

Para gerar um cliente, validar chamadas ou ler todos os detalhes, use o contrato OpenAPI.

O contrato está em openapi.yaml. O Postman e o Insomnia também o importam (pelo endereço do arquivo ou por upload). No Swagger Editor (no navegador), baixe o arquivo e importe por upload.

Não gere cliente para as rotas com o selo "em breve": os parâmetros e a resposta delas são provisórios, e o contrato não descreve a resposta de sucesso delas (só os erros).

A API é feita para servidores e aplicativos. Ela não funciona chamada diretamente do JavaScript de uma página web.

O mesmo contrato existe em JSON, em openapi.json, para geradores de código e agentes de IA.

Coleção do Postman e do Insomnia

Para chamar a API sem escrever código, use a coleção pronta. Ela é gerada do contrato e traz uma requisição para cada rota que já está no ar, com as pastas na ordem deste guia: Conta e acesso (o ping e as lojas), Eventos e ingressos e Vendas e recebimentos.

  1. Baixe a coleção (o arquivo postman.json).
  2. Postman: clique em Import e escolha o arquivo. Insomnia: use Import e escolha o mesmo arquivo (o Insomnia lê coleções do Postman).
  3. Abra as variáveis da coleção e preencha api_key com a chave que o lojista copiou. base_url já vem com o endereço de produção.
    • Postman: clique no nome da coleção, na barra lateral, e abra a aba Variables. Escreva a chave em api_key (na coluna Current value, se a sua versão mostrar duas colunas) e salve.
    • Insomnia: as variáveis da coleção importada ficam no ambiente base dela. Abra o seletor de ambiente, no alto da barra lateral, entre em Manage Environments e edite api_key no Base Environment.
  4. Envie Quem é esta chave: a resposta 200 confirma que a chave funciona. Cada requisição traz testes prontos (a aba Test Results do Postman): o status 200 e a presença dos campos principais da resposta.
  5. Envie As lojas da chave e copie o id da loja que você quer consultar para a variável place_id. As outras requisições usam essa variável no parâmetro place.
  6. Para filtrar os recebimentos por um evento: envie Os eventos de uma loja, copie o id do evento para a variável event_id e, em Recebimentos por método de pagamento, marque o parâmetro event, que vem desligado.

place_id e event_id vêm vazios, de propósito: um identificador de exemplo não existe na sua conta. Enviada com place_id vazio, a requisição responde 422 com param igual a place, que é o lembrete de preencher a variável.

O período é calculado a cada envio. Um script da coleção grava em date_from e date_to os últimos 7 dias, em UTC, e os recebimentos usam essas variáveis em from e to. Para outro período, troque o valor do parâmetro na requisição. Se o seu programa não rodar o script (o 422 vem com param igual a from), preencha as duas variáveis à mão, no formato de Datas e fuso.

A autorização (Bearer com a variável api_key) está definida só na coleção: toda requisição, pronta ou nova, herda a chave (no Postman, a aba Authorization da requisição fica em "Inherit auth from parent"). Nenhuma requisição leva o cabeçalho Authorization próprio.

Cada requisição traz também respostas de exemplo: a 200 e os erros 401 (chave errada, e a chave que deixou de valer, com reason), 422 (parâmetro desconhecido) e 429 (limite de uso). As rotas que pedem permissão trazem ainda o 403, com required_permission.

A coleção nunca traz uma chave: api_key vem vazia. As chamadas vão para a produção e contam no limite de uso. As rotas com o selo "em breve" não estão nela; elas entram quando forem ligadas (baixe o arquivo de novo).

Idioma. A coleção e este guia estão em português. O contrato OpenAPI está em inglês, como os programas que o leem esperam: o Postman, o Insomnia e os geradores de cliente mostram só os campos padrão (summary, description) e ignoram a tradução que o contrato carrega à parte. Para ver as requisições em português no Postman ou no Insomnia, importe a coleção, e não o contrato.

llms.txt

O arquivo llms.txt é um resumo desta API em texto puro, para agentes de IA: o endereço base, de onde vem a chave, as rotas no ar e os links para o contrato e para este guia. Aponte o seu agente para ele antes de pedir que escreva a integração.

Segurança

Trate a chave como uma senha: quem a tem lê, nas lojas que ela alcança, tudo o que ela tem permissão para ler.

Onde a chave deve ficar

Guarde a chave num servidor seu, não dentro do aplicativo. O seu servidor consulta a API e entrega o resultado aos aplicativos.

Dois motivos:

  • Segurança. Uma chave embutida num aplicativo (APK, por exemplo) pode ser extraída por qualquer pessoa que tenha o arquivo.
  • Limite de uso. A chave tem um limite de chamadas por minuto (veja Limites de uso). Se cada aparelho chamar a API direto, todos dividem o mesmo limite. Com o servidor no meio, cem aparelhos lendo o mesmo dado custam uma chamada.

Chave dentro do aplicativo funciona tecnicamente, mas só faz sentido numa instalação única e controlada (um tablet no balcão, por exemplo).

Uma conta pode ter até 20 chaves ativas. Não use uma chave por aparelho.

Rotação da chave

Tudo é feito pelo lojista, na tela "Chaves da API".

  • Rotacionar (na tela, "Rotacionar chave") gera uma chave nova. A anterior continua valendo por 24 horas, para dar tempo de atualizar o seu sistema sem parar.
  • Rotacionar invalidando a atual (a opção "Invalidar a chave atual agora, sem esperar as 24 horas") gera a chave nova e desliga todas as anteriores na hora: a que estava em uso e a que ainda estava nas 24 horas. O seu sistema fica parado até receber a chave nova.
  • Pausar (em "Editar", desligar "Chave ativa") desliga a chave sem trocá-la. Quando o lojista reativa, a mesma chave volta a funcionar, sem você fazer nada. Pausar dentro das 24 horas de uma rotação encerra o prazo da chave anterior: ela não volta com a reativação.
  • Revogar desliga a chave na hora, para sempre. Dentro das 24 horas de uma rotação, revogar desliga as duas: a chave atual e a anterior passam a responder 401 com reason igual a revoked.
  • Validade é obrigatória: 12 meses por padrão, no máximo 24. A chave vale até as 23:59 da data escolhida, no horário de Brasília. Antes disso, o lojista estende a validade na tela (a chave continua a mesma) ou cria outra.
  • O atendimento da Gototem pode revogar uma chave (por exemplo, num vazamento), mas não cria nem rotaciona chave.

O que o lojista muda na chave (tirar uma permissão, restringir as lojas, pausar, revogar) passa a valer em segundos; no pior caso, em cerca de um minuto.

Perdeu a chave? Ela não pode ser mostrada de novo.

  • Se acabou de ser criada: peça ao lojista para revogar e criar outra.
  • Se foi rotacionada e a chave nova se perdeu: peça para rotacionar de novo. Enquanto as 24 horas da chave anterior estiverem correndo, a segunda rotação sempre desliga as chaves anteriores na hora (a tela não oferece outra opção), e a chave que o seu sistema ainda usa deixa de funcionar: combine o momento. Se o lojista pausou e reativou a chave nesse intervalo, as 24 horas já foram encerradas e a rotação seguinte volta a ser a normal (a chave em uso ganha 24 horas).

A chave vazou? Peça ao lojista para revogá-la imediatamente.

Como saber o estado da chave

A API diz ao seu sistema o que o lojista mudou na chave, sem depender de ele avisar.

O que o lojista mudou e o que o seu sistema vê
O que o lojista fez O que o seu sistema vê O que fazer
Tirou uma permissão da chave a rota dela responde 403 com required_permission; o ping deixa de listá-la em credential.permissions pedir a permissão de volta
Tirou uma loja da chave a loja some de /places; a chamada com o id dela responde 404 com param igual a place, igual à de um id errado. Para distinguir, releia /places tirar a loja das suas consultas, ou pedir a loja de volta
Pausou a chave 401 com reason igual a paused pedir para reativar
Reativou a chave a mesma chave volta a responder nada
Rotacionou (a anterior vale 24 horas) a chave anterior segue funcionando; o ping devolve "superseded": true e, em superseded_until, o instante em que ela para; toda resposta traz Deprecation e Sunset trocar pela chave nova antes desse instante
Pausou a chave dentro dessas 24 horas a chave anterior responde 401 com reason igual a replaced desde a pausa, e não volta com a reativação; a chave nova responde paused até ser reativada usar a chave nova
Rotacionou invalidando a atual a chave que estava em uso responde 401 com reason igual a replaced, na hora usar a chave nova
Rotacionou pela segunda vez a chave de duas rotações atrás responde 401 sem reason (a API só guarda a atual e a anterior) e conta como chave que não existe no bloqueio de IP usar a chave mais nova
Revogou a chave 401 com reason igual a revoked, para sempre. Dentro das 24 horas de uma rotação, a chave atual e a anterior param juntas, e as duas respondem revoked pedir outra chave
Deixou a validade vencer 401 com reason igual a expired; antes disso, a data está em credential.expires_at, no ping pedir para estender a validade (a chave continua a mesma) ou criar outra

Acompanhe a validade. Confira credential.expires_at de tempos em tempos (uma vez por dia basta) e avise o lojista com antecedência.

A chave nova é da mesma credencial? credential.id identifica a credencial, e não o segredo: ele continua o mesmo quando a chave é rotacionada, e é outro numa chave criada do zero. É por ele que o seu sistema distingue "recebi a chave nova da mesma credencial" de "recebi outra credencial".

Os cabeçalhos da chave substituída. Vêm em toda resposta que a chave anterior de uma rotação autenticou, de sucesso ou de erro (403, 404 de loja ou de evento, 422, 429 do limite, 501, 503):

Cabeçalhos da chave que foi substituída
Deprecation: true
Sunset: Fri, 09 Oct 2026 14:20:00 GMT

Sunset é o mesmo instante de superseded_until. Os dois cabeçalhos não vêm com a chave atual, nem nas respostas que saem antes de a chave ser conferida (400, 401, 405, 404 de endereço errado, 429 de IP barrado).

O reason do 401. Só vem quando a chave já funcionou:

Motivos do 401
reason O que aconteceu
revoked o lojista revogou a chave
expired a validade venceu
paused o lojista pausou a chave, e esta é a chave atual dela
replaced era a chave anterior de uma rotação e o prazo dela acabou: passaram as 24 horas, ou o lojista rotacionou invalidando a atual, ou pausou a chave nesse intervalo
Resposta 401 com o motivo
{
  "code": "invalid_token",
  "reason": "revoked",
  "type": "about:blank",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Invalid or missing API key."
}

401 sem reason quase sempre é erro do seu lado: chave ausente, digitada errada ou que nunca existiu. Há duas exceções: a chave de duas rotações atrás e a conta do lojista desativada. Raramente, uma chave que deixou de valer responde uma vez sem reason, e essa resposta pode contar no bloqueio do IP.

Ao receber um 401 sem reason:

  1. Pare de repetir a chamada. Chave ausente, fora do formato ou não reconhecida conta para o bloqueio do IP: 20 em 60 segundos (veja Os casos do 429).
  2. Confira se a chave enviada é a certa e está inteira (sem corte, espaço ou aspas a mais).
  3. Tente uma vez depois de um minuto.
  4. Se o 401 sem reason persistir, fale com o lojista.

Com mais de um motivo, vale o primeiro desta ordem: revoked, expired, paused, replaced. Há uma exceção: a chave anterior de uma rotação cujo prazo acabou responde replaced mesmo com a chave pausada, porque ela não volta com a reativação. Valores novos podem aparecer: trate um desconhecido como "a chave não vale; falar com o lojista".

Glossário

As palavras que esta documentação usa, e o que cada uma quer dizer aqui.

Glossário
Termo O que é
Chave o segredo que identifica a sua integração; começa com gtpk_ e vai no cabeçalho Authorization. No painel: "Chaves da API"
Conta o cadastro do lojista na Gototem. Toda chave pertence a uma conta e só lê dados dela
Lojista o dono da conta no painel da Gototem; é ele quem cria e altera as chaves
Loja um ponto de venda da conta. No painel chama-se "Estabelecimento"; nas rotas e nos campos, place
Evento um evento cadastrado numa loja (um show, uma festa), que pode ser usado para filtrar os recebimentos. Nas rotas e nos campos, event
Rota um endereço da API, como GET /v1/api-public/events
Permissão o que o lojista marca na chave para liberar uma rota de dados, como event.view. Sem ela, a rota responde 403
Parâmetro um valor enviado na chamada, depois do ? do endereço, como place e from
Campo um valor que vem na resposta, como payment_method e amount
Recebimento um pagamento que entrou: uma venda paga com dois cartões gera dois recebimentos
TEF pagamento em cartão ou Pix feito na maquininha integrada ao equipamento da loja
POS pagamento em maquininha não integrada, lançado à parte no caixa
Cashless cartão ou pulseira pré-paga: o cliente carrega um saldo e consome com ele
Comanda conta aberta de um cliente, paga no fim
Classe de limite o grupo de limite de uso de uma rota: default ou heavy (veja Limites de uso)

Por que a rota se chama sales e devolve recebimentos. A rota /sales/by-payment-method fica no grupo de vendas porque responde à pergunta "quanto as vendas renderam, por método de pagamento". O que ela soma, porém, são os recebimentos (os pagamentos), e não os pedidos: é o mesmo número dos relatórios de recebimentos do painel.

Três grafias para o mesmo assunto. Cada lugar segue a regra dele, e elas não mudam:

  • a rota usa hífens e plural: sales/by-payment-method;
  • a permissão usa hífens, singular e a ação no fim: sale-by-payment-method.view;
  • o campo e o parâmetro usam sublinhado: payment_method, next_cursor.

Formatos e convenções

O que vale para todas as rotas: como a API escreve datas, valores em dinheiro e identificadores, e como as listas longas são divididas em páginas.

Datas e fuso

Toda data de entrada precisa dizer o fuso, no formato ISO 8601:

Formato aceito
2026-10-01T00:00:00-03:00     (horário de Brasília)
2026-10-01T03:00:00Z          (o mesmo instante, em UTC)

Data sem o final (-03:00 ou Z) é recusada com 422.

Em fusos positivos, codifique o + como %2B (ou use Z): na URL, um + cru vira espaço e a data é recusada. O - dos fusos do Brasil não precisa de codificação.

O período é sempre início incluído, fim não incluído. Para pegar um dia inteiro, o to é a meia-noite do dia seguinte:

Datas e fuso
Período from to
Hoje (08/10) 2026-10-08T00:00:00-03:00 2026-10-09T00:00:00-03:00
Ontem 2026-10-07T00:00:00-03:00 2026-10-08T00:00:00-03:00
Este mês 2026-10-01T00:00:00-03:00 2026-11-01T00:00:00-03:00

Use o timezone que /places devolve para cada loja. Em Kotlin:

Período de hoje e do mês, no fuso da loja
import java.time.LocalDate
import java.time.ZoneId
import java.time.format.DateTimeFormatter

fun periodoDeHoje(timezone: String): Pair<String, String> {
    val zona = ZoneId.of(timezone)                       // ex.: "America/Sao_Paulo"
    val hoje = LocalDate.now(zona)
    val formato = DateTimeFormatter.ISO_OFFSET_DATE_TIME
    val inicio = hoje.atStartOfDay(zona).format(formato)           // 2026-10-08T00:00:00-03:00
    val fim = hoje.plusDays(1).atStartOfDay(zona).format(formato)  // 2026-10-09T00:00:00-03:00
    return inicio to fim
}

fun periodoDoMes(timezone: String): Pair<String, String> {
    val zona = ZoneId.of(timezone)
    val primeiroDia = LocalDate.now(zona).withDayOfMonth(1)
    val formato = DateTimeFormatter.ISO_OFFSET_DATE_TIME
    return primeiroDia.atStartOfDay(zona).format(formato) to
        primeiroDia.plusMonths(1).atStartOfDay(zona).format(formato)
}

As datas da resposta vêm sempre em UTC, com Z no final. Vale também para a validade da chave: as 23:59:59 de 10/10/2027 em Brasília chegam em expires_at como 2027-10-11T02:59:59Z.

Um recebimento recém-registrado pode levar alguns segundos para aparecer, e venda feita sem conexão chega mais tarde: veja Vendas que chegam depois.

Dinheiro

Todo valor em dinheiro vem como texto, com ponto e duas casas decimais ("1234.50"), ao lado da moeda. Converta para um tipo decimal e nunca para ponto flutuante: somar valores como float perde centavos. Os exemplos de Recebimentos por método de pagamento mostram a conversão em cada linguagem.

Identificadores

Identificadores (id de loja, evento, chave e conta) são texto de letras e números, hoje com até 64 caracteres. Guarde-os como texto e devolva exatamente como recebeu; não os trate como número nem tente montá-los.

Paginação

Lista que pode ser longa vem em páginas, por cursor: a resposta traz next_cursor, e a página seguinte é a mesma chamada com cursor=<next_cursor>. next_cursor igual a null quer dizer que não há mais nada. Não monte nem altere o cursor: devolva-o como veio. Hoje a rota paginada é a de eventos; o passo a passo está em Os eventos de uma loja.

Limites de uso

Limites de uso
O que Limite
Recebimentos por método e as demais rotas de vendas (classe heavy) 10 chamadas por minuto por chave
ping, places, events e demais rotas (classe default) 60 chamadas por minuto por chave
Soma de todas as chaves da conta 30 por minuto nas rotas de vendas, 180 nas demais
Consultas de vendas em andamento ao mesmo tempo 2 por conta
Período máximo por consulta 120 dias (sem limite com event)
Tempo máximo de uma consulta 15 segundos em recebimentos, 3 segundos na lista de eventos; acima disso, 503
Chaves ativas por conta 20
Validade de uma chave 12 meses por padrão, até 24 meses

Toda resposta autenticada traz o seu saldo:

Cabeçalhos da resposta
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1791481927

Uma consulta por minuto é suficiente para um painel "ao vivo". Guarde a última resposta e reaproveite.

  • X-RateLimit-Remaining é o menor saldo entre o da chave e o da conta: com várias chaves na mesma conta, é o da conta que acaba primeiro.
  • X-RateLimit-Reset é um instante em segundos desde 1970 (tempo Unix) e é um limite máximo: a janela é móvel (os últimos 60 segundos), então o saldo pode voltar antes.

Erros também contam no limite quando a chave já foi reconhecida (422, 404 de loja ou de evento, 403, e o 501 das rotas "em breve", na classe da rota): ao depurar em laço, espace as chamadas.

Nas 24 horas depois de uma rotação, a chave nova e a anterior têm cada uma o seu saldo por chave; o limite da conta vale para as duas somadas.

Ao passar de um limite, a API responde 429 com o cabeçalho Retry-After (em segundos) e o code rate_limited. Espere esse tempo antes de tentar de novo: insistir sem esperar mantém o bloqueio.

Os casos do 429

Os casos do 429
Quem recusou Retry-After X-RateLimit-Limit e X-RateLimit-Remaining detail
A cota da chave na classe da rota 60 (o tamanho da janela) o limite da chave; 0 Rate limit exceeded for this API key. Retry after the time in the Retry-After header.
O teto da conta (a soma de todas as chaves) 60 o limite da sua chave, e não o da conta; 0 Rate limit exceeded for this account. Retry after the time in the Retry-After header.
Consultas de vendas simultâneas: a conta já tem 2 em andamento, ou o teto da API inteira está cheio 5 o limite da chave; o saldo real, já descontada essa chamada Too many concurrent requests for this account on this endpoint. Wait for the ongoing requests to finish and retry after the time in the Retry-After header. (o mesmo texto nos dois casos)
IP barrado por chaves erradas (20 em 60 segundos) a espera real, de 1 a 60 20; 0 Rate limit exceeded for this client. Retry after the time in the Retry-After header.
Muitas chamadas do mesmo IP ainda não autenticadas ao mesmo tempo (as que estão em andamento mais as falhas do último minuto, acima de 40) 60 40; 0 Rate limit exceeded for this client. Retry after the time in the Retry-After header.
O teto absoluto por chave e classe (bem acima da cota), conferido antes de a chave ser lida 60 o valor desse teto; 0 Rate limit exceeded for this API key. Retry after the time in the Retry-After header.
O limite por IP das páginas desta documentação (30 por minuto, somadas as três) 60 não vêm Rate limit exceeded for this client. Retry after the time in the Retry-After header.

No código basta esperar o Retry-After; a diferença serve para investigar, e o code é sempre rate_limited.

O IP barrado. Contam as chamadas sem chave, com chave fora do formato ou com chave que não existe (chave revogada, vencida, pausada ou substituída não conta; uma chave de duas rotações atrás conta). O bloqueio dura até essas falhas saírem do último minuto. Ao fim da espera sobra pouca folga: a janela é móvel, só a falha mais antiga saiu dela, e uma falha nova bloqueia o IP de novo. O 404 de endereço que não existe e o 405 não contam como falha e não são barrados: um IP bloqueado continua recebendo os dois. Um servidor que faz poucas chamadas por vez não chega perto do limite de chamadas simultâneas ainda não autenticadas.

O teto absoluto. Fica bem acima da cota e só aparece para uma chave em laço, que continua chamando depois do 429 da cota: as chamadas recusadas também contam, e ao passar do teto o 429 sai antes de a chave ser lida, com o teto em X-RateLimit-Limit.

Não dispare várias consultas de vendas em paralelo. A recusa por consultas simultâneas também conta no limite por minuto: repetir em paralelo esgota a cota e o 429 seguinte pede 60 segundos. Para várias lojas, faça as chamadas uma depois da outra. Em pico, o teto da API inteira pode recusar uma consulta de vendas mesmo sem outra sua em andamento.

Como esperar depois de um 429

Ao receber 429 (ou 503), leia o cabeçalho Retry-After, espere esse número de segundos e só então repita. Limite o número de tentativas: um laço sem fim mantém o bloqueio.

curl
# -i mostra os cabeçalhos: procure Retry-After e X-RateLimit-Remaining
curl -s -i "$GOTOTEM_API/v1/api-public/ping" \
  -H "Authorization: Bearer $GOTOTEM_API_KEY"

# --retry repete a chamada depois de um 429 ou 503, esperando o Retry-After que a API mandou
curl -s --retry 3 "$GOTOTEM_API/v1/api-public/ping" \
  -H "Authorization: Bearer $GOTOTEM_API_KEY"
PowerShell
$headers = @{ Authorization = "Bearer $env:GOTOTEM_API_KEY" }

foreach ($tentativa in 1..3) {
    try {
        $resposta = Invoke-RestMethod -Uri "$env:GOTOTEM_API/v1/api-public/ping" -Headers $headers
        break
    } catch {
        $recusa = $_.Exception.Response
        if ($null -eq $recusa -or [int]$recusa.StatusCode -notin 429, 503) { throw }
        $espera = [int]($recusa.Headers.GetValues('Retry-After') | Select-Object -First 1)
        Start-Sleep -Seconds $espera
    }
}

$resposta
Python (requests)
import os
import time

import requests

API = os.environ["GOTOTEM_API"]
HEADERS = {"Authorization": "Bearer " + os.environ["GOTOTEM_API_KEY"]}


def chamar(caminho, params=None, tentativas=3):
    for _ in range(tentativas):
        resposta = requests.get(API + caminho, headers=HEADERS, params=params, timeout=30)
        if resposta.status_code not in (429, 503):
            return resposta
        time.sleep(int(resposta.headers.get("Retry-After", "60")))
    return resposta


resposta = chamar("/v1/api-public/ping")
print(resposta.status_code, resposta.headers.get("X-RateLimit-Remaining"))
JavaScript (Node 18+, arquivo .mjs)
const API = process.env.GOTOTEM_API;
const HEADERS = { Authorization: "Bearer " + process.env.GOTOTEM_API_KEY };

async function chamar(caminho, tentativas = 3) {
  let resposta;
  for (let i = 0; i < tentativas; i++) {
    resposta = await fetch(API + caminho, { headers: HEADERS });
    if (resposta.status !== 429 && resposta.status !== 503) return resposta;
    const espera = Number(resposta.headers.get("Retry-After") ?? "60");
    await new Promise((resolve) => setTimeout(resolve, espera * 1000));
  }
  return resposta;
}

const resposta = await chamar("/v1/api-public/ping");
console.log(resposta.status, resposta.headers.get("X-RateLimit-Remaining"));
PHP (curl)
<?php
$api = getenv('GOTOTEM_API');
$chave = getenv('GOTOTEM_API_KEY');

function chamar(string $url, string $chave, int $tentativas = 3): array
{
    for ($i = 0; $i < $tentativas; $i++) {
        $cabecalhos = [];
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $chave],
            CURLOPT_TIMEOUT => 30,
            // guarda os cabeçalhos da resposta, com o nome em minúsculas
            CURLOPT_HEADERFUNCTION => function ($ch, $linha) use (&$cabecalhos) {
                $partes = explode(':', $linha, 2);
                if (count($partes) === 2) {
                    $cabecalhos[strtolower(trim($partes[0]))] = trim($partes[1]);
                }
                return strlen($linha);
            },
        ]);
        $corpo = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if ($status !== 429 && $status !== 503) {
            break;
        }
        sleep((int) ($cabecalhos['retry-after'] ?? 60));
    }

    return [$status, $cabecalhos, $corpo];
}

[$status, $cabecalhos] = chamar($api . '/v1/api-public/ping', $chave);
echo $status, ' ', $cabecalhos['x-ratelimit-remaining'] ?? '', PHP_EOL;

Erros

Todo erro vem em JSON, com um campo code estável. Programe contra o code; o detail é um texto para pessoas, vem em inglês e pode mudar. Para mostrar o erro ao seu usuário, escreva a sua mensagem a partir do code.

Resposta de erro
{
  "code": "invalid_parameter",
  "param": "from",
  "type": "about:blank",
  "title": "Unprocessable Entity",
  "status": 422,
  "detail": "\"from\" is not a valid date."
}
Campos da resposta de erro
Campo O que é
code o código estável do erro (tabela abaixo)
param o nome do parâmetro que você precisa corrigir: no 422, e no 404 de loja (place) ou de evento (event). Não vem quando não há um parâmetro culpado, nem no 404 de endereço que não existe
reason só no 401 de uma chave que já funcionou: por que ela parou (veja Como saber o estado da chave)
required_permission só no 403 com forbidden_permission: a permissão que falta na chave para a rota chamada (veja abaixo)
type sempre about:blank: quem identifica o erro é o code
title, status o nome e o número do status HTTP
detail a explicação, em inglês, para quem está depurando

Toda resposta da API pública (as que têm code, e as de sucesso) traz o cabeçalho X-Request-Id. Informe esse valor ao suporte ao relatar um problema (veja Suporte).

Todo 401 traz também o cabeçalho WWW-Authenticate: Bearer realm="gototem-public-api", que diz o esquema de autenticação. Ele é sempre igual e não diz o motivo: o motivo, quando vem, está em reason.

Qual parâmetro vem em param:

  • parâmetro desconhecido: o primeiro desconhecido;
  • valor inválido (data sem fuso, limit fora de 1 a 100, lista onde só cabe um): o próprio parâmetro;
  • parâmetro enviado mais de uma vez: o próprio parâmetro;
  • from sem to, ou to sem from: o que faltou;
  • from depois de to: from;
  • período acima do limite: to;
  • search vazio, com mais de 100 caracteres ou com caractere de controle ou invisível: search;
  • cursor inválido, ou de outra loja: cursor;
  • no 404: place quando a chave não alcança a loja (ou ela não existe), event quando o evento não é daquela loja (ou não existe). Com a loja e o evento errados na mesma chamada, vem place: a loja é conferida primeiro.
Códigos de erro
HTTP code O que aconteceu O que fazer
400 conflicting_credentials outro cabeçalho de identidade da Gototem foi junto (lista abaixo da tabela), com qualquer valor, em qualquer endereço da API enviar só o Authorization
401 invalid_token chave ausente, errada, revogada, pausada, vencida ou substituída conferir a chave; se vier reason, o caso é com o lojista (veja Como saber o estado da chave)
403 forbidden_permission a chave não tem a permissão desta rota pedir ao lojista para marcar na chave a permissão que vem em required_permission (veja O que cada permissão libera)
404 not_found place que a chave não alcança, ou event que não é daquela loja (com param igual a place ou event); ou endereço que não existe (erro de digitação na rota, barra sobrando no fim, falta do /v1), sem param com param, corrigir esse parâmetro; sem param, conferir o endereço
405 method_not_allowed outro método que não GET, inclusive HEAD e OPTIONS (veja abaixo da tabela) usar GET
422 invalid_parameter parâmetro faltando (inclusive place), desconhecido, enviado mais de uma vez, data sem fuso, período acima de 120 dias (sem event), mais de uma loja ou de um evento, identificador fora do formato, ou cursor inválido ou de outra loja corrigir o parâmetro que vem em param
429 rate_limited limite de uso atingido esperar o Retry-After
500 internal_error falha inesperada no servidor tentar de novo mais tarde; se persistir, enviar o X-Request-Id ao suporte
501 not_implemented rota do contrato ainda não disponível aguardar
503 temporarily_unavailable a consulta não terminou a tempo (em recebimentos e na lista de eventos), ou, só em recebimentos, o controle de consultas simultâneas ficou indisponível por um momento (veja Os dois casos do 503) esperar o Retry-After e repetir; se persistir, reduzir o período

Parâmetro desconhecido é sempre recusado (nunca ignorado), para um erro de digitação não virar uma resposta com mais dados do que você pediu. O detail cita no máximo 5 nomes desconhecidos (cada um cortado em 40 caracteres) e diz quantos mais havia.

Cada parâmetro vai uma vez só. Um parâmetro repetido (place=A&place=B, limit=5&limit=6, mesmo com o valor igual, e também place[]=A&place[]=B) responde 422 com param igual ao nome dele e o detail Parameter "<nome>" was sent more than once. A API não escolhe um dos valores por você. A forma de lista enviada uma vez (place[]=A) também é recusada com 422 e o mesmo param, mas como valor inválido: em place, o detail é "place" must be a place identifier.

Só GET. Toda rota da API, inclusive as desta documentação, aceita só GET. Qualquer outro método, inclusive HEAD (que muitos monitores de disponibilidade usam) e OPTIONS, responde 405 com code method_not_allowed e o cabeçalho Allow: GET, com ou sem chave, e não conta no limite de uso. Um monitor que manda HEAD vai registrar a API como fora do ar o tempo todo: para monitorar, use GET /v1/api-public/ping com uma chave válida (resposta pequena, classe default dos Limites de uso; uma chamada por minuto basta).

Endereço que não existe (por exemplo, /v1/api-public/pings, ou /v1/api-public/ping/ com a barra no fim) responde 404 com code not_found, no mesmo formato JSON de todos os outros erros e com X-Request-Id, com ou sem o cabeçalho Accept. O conserto é conferir o endereço.

Cabeçalhos de identidade que a API recusa. São os que outros sistemas da Gototem usam: Account-Token, Api-Token, Producer-Token, EventOrganizer-Token, EventOrganizer-Host e Online-Catalog-Identifier. Qualquer um deles, com qualquer valor, em qualquer endereço que comece com /v1/api-public/ (inclusive o desta documentação), responde 400 com conflicting_credentials. Isso costuma acontecer quando o mesmo cliente HTTP é reaproveitado para chamar outra API da Gototem e leva os cabeçalhos padrão dela: crie um cliente só para esta API.

O Accept não muda a resposta. As rotas respondem JSON com qualquer Accept (application/xml, text/html, nenhum). Há uma exceção: um Accept com tipo de mídia versionado de outra versão, no formato application/vnd.<nome>.v<número>+json (por exemplo, application/vnd.exemplo.v2+json), responde 404, porque a versão pedida no Accept vence a do endereço.

Duas respostas vêm sem code:

  • GET com corpo e Content-Type que não é JSON (um 415 sem code). Só acontece com uma chave válida (sem ela, a resposta é o 401), vem com X-Request-Id e X-RateLimit-* e conta no limite de uso. Não envie corpo nem Content-Type.
  • Falha inesperada (5xx sem code), do servidor que fica à frente da API: pode vir sem o X-Request-Id. Tente de novo depois.

O 403 diz qual permissão falta

Quando a chave é válida mas não tem a permissão da rota chamada, o erro traz o campo required_permission, com o nome da permissão que falta:

Resposta do 403
{
  "code": "forbidden_permission",
  "required_permission": "event.view",
  "type": "about:blank",
  "title": "Forbidden",
  "status": 403,
  "detail": "This API key does not have the permission for this route."
}
  • O valor é o mesmo nome que aparece na coluna "Nome da permissão" de O que cada permissão libera, na referência de cada rota e na lista credential.permissions do ping.
  • Ele nomeia uma permissão: a da rota que você chamou. Não é a lista de tudo o que falta na chave.
  • O conserto não é no seu código. Quem muda as permissões de uma chave é o dono da conta: passe a ele o nome que veio em required_permission (a tabela de O que cada permissão libera mostra como ela aparece na tela dele). A mesma chave passa a funcionar depois que ele salvar; não é preciso trocar de chave.
  • O campo só vem nesse erro. Trate-o como texto e não o compare com uma lista fixa: rotas novas trazem permissões novas.

Os dois casos do 503

O 503 tem sempre o mesmo code (temporarily_unavailable) e o mesmo Retry-After (60 segundos). O que muda é o detail, que diz qual dos dois casos aconteceu:

  • A consulta não terminou a tempo (em recebimentos e na lista de eventos): ela passou do tempo máximo, ou o banco de dados ficou indisponível por um momento. O detail é The request could not be completed in time. Retry after the time in the Retry-After header.
  • O controle de consultas simultâneas ficou indisponível (só em recebimentos), e a API preferiu não rodar a consulta sem ele. O detail é This endpoint is temporarily unable to accept requests. Retry after the time in the Retry-After header.

No seu código, trate os dois do mesmo jeito: espere o Retry-After e repita a mesma chamada (veja Como esperar depois de um 429; a espera é a mesma). A diferença serve para investigar: no primeiro caso, se o erro persistir, reduza o período consultado; no segundo, reduzir o período não ajuda, porque nenhuma consulta chegou a rodar. Ao pedir ajuda, envie o detail junto com o X-Request-Id.

Continue decidindo pelo code, nunca pelo texto do detail: o texto pode mudar.

Como tratar um erro no código

A regra é a mesma em qualquer linguagem: olhe o status; se não for 200, leia o code do corpo e decida por ele. Guarde o X-Request-Id no seu log. Uma resposta de erro sem code é um dos dois casos de Erros: trate pelo status.

O exemplo chama /events sem o place, de propósito, para provocar um 422:

curl
# -i mostra o status e os cabeçalhos (entre eles o X-Request-Id) antes do corpo do erro
curl -s -i "$GOTOTEM_API/v1/api-public/events" \
  -H "Authorization: Bearer $GOTOTEM_API_KEY"
PowerShell
try {
    $eventos = Invoke-RestMethod -Uri "$env:GOTOTEM_API/v1/api-public/events" `
        -Headers @{ Authorization = "Bearer $env:GOTOTEM_API_KEY" }
} catch {
    $recusa = $_.Exception.Response
    if ($null -eq $recusa) { throw }   # sem resposta: falha de rede

    # o corpo do erro: no PowerShell 7 vem em ErrorDetails; no Windows PowerShell 5.1, no fluxo da resposta
    $corpo = $_.ErrorDetails.Message
    if (-not $corpo) { $corpo = (New-Object IO.StreamReader($recusa.GetResponseStream())).ReadToEnd() }
    $erro = $corpo | ConvertFrom-Json
    $requestId = $recusa.Headers.GetValues('X-Request-Id') | Select-Object -First 1

    "HTTP $([int]$recusa.StatusCode) code=$($erro.code) param=$($erro.param) request_id=$requestId"
}
Python (requests)
import os
import requests

API = os.environ["GOTOTEM_API"]
HEADERS = {"Authorization": "Bearer " + os.environ["GOTOTEM_API_KEY"]}

resposta = requests.get(API + "/v1/api-public/events", headers=HEADERS, timeout=30)

if resposta.ok:
    print(resposta.json())
else:
    try:
        erro = resposta.json()
    except ValueError:
        erro = {}  # corpo que não é JSON: a resposta não veio da API pública
    print(
        "HTTP", resposta.status_code,
        "code=", erro.get("code"),
        "param=", erro.get("param"),
        "request_id=", resposta.headers.get("X-Request-Id"),
    )
JavaScript (Node 18+, arquivo .mjs)
const API = process.env.GOTOTEM_API;
const HEADERS = { Authorization: "Bearer " + process.env.GOTOTEM_API_KEY };

const resposta = await fetch(API + "/v1/api-public/events", { headers: HEADERS });

if (resposta.ok) {
  console.log(await resposta.json());
} else {
  // corpo que não é JSON: a resposta não veio da API pública
  const erro = await resposta.json().catch(() => ({}));
  console.log(
    "HTTP", resposta.status,
    "code=", erro.code,
    "param=", erro.param,
    "request_id=", resposta.headers.get("X-Request-Id"),
  );
}
PHP (curl)
<?php
$api = getenv('GOTOTEM_API');
$chave = getenv('GOTOTEM_API_KEY');

$requestId = null;
$ch = curl_init($api . '/v1/api-public/events');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $chave],
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HEADERFUNCTION => function ($ch, $linha) use (&$requestId) {
        if (stripos($linha, 'X-Request-Id:') === 0) {
            $requestId = trim(substr($linha, strlen('X-Request-Id:')));
        }
        return strlen($linha);
    },
]);
$corpo = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($status === 200) {
    print_r(json_decode($corpo, true));
} else {
    // corpo que não é JSON vira array vazio: a resposta não veio da API pública
    $erro = json_decode((string) $corpo, true) ?: [];
    echo 'HTTP ', $status, ' code=', $erro['code'] ?? '', ' param=', $erro['param'] ?? '',
        ' request_id=', $requestId, PHP_EOL;
}

Versões e compatibilidade

  • Dentro da v1 só fazemos mudanças que não quebram quem já integra: campos novos, rotas novas, códigos novos em listas já documentadas. Ignore campos que você não conhece e não falhe ao receber um código novo.

  • Qualquer mudança que quebre vai para uma versão nova (v2), e a anterior segue no ar por um período de convivência, avisado nos cabeçalhos Deprecation e Sunset. Hoje só existe a v1, e esses dois cabeçalhos querem dizer outra coisa: a chamada usou uma chave que foi trocada (veja Como saber o estado da chave).

  • O histórico de mudanças está em Histórico de mudanças e, em inglês, no início do contrato.

  • Rota com o selo "em breve" (responde 501) fica fora dessa promessa: os parâmetros e a resposta dela são provisórios e podem mudar quando a rota for ligada. A mudança entra em Histórico de mudanças. Por isso não vale gerar cliente para ela a partir do contrato, que nem descreve a resposta de sucesso dela.

O /v1 no endereço é obrigatório.

Histórico de mudanças

O que mudou na API para quem integra, do mais recente para o mais antigo. Quebra é mudança que pede ajuste em quem já integra; acréscimo não pede.

  • 2026-10-12 — A documentação saiu da API e passou a ser publicada só no portal do desenvolvedor: os endereços /v1/api-public/docs, /v1/api-public/docs/openapi.yaml e /v1/api-public/docs/postman.json respondem 404, e o detail do 404 da raiz (/v1/api-public) deixou de citar o endereço da documentação. Nenhuma rota de dados mudou.
  • 2026-10-10 — Versão inicial da API pública. As rotas que estão no ar e as que ainda vêm com o selo "em breve" estão descritas neste guia e na referência da API.

Recebimentos por método de pagamento

O caminho até os recebimentos é sempre o mesmo: places dá o id da loja, events dá o id do evento (só se quiser filtrar por um) e sales/by-payment-method devolve os números.

GET/v1/api-public/sales/by-payment-methodPermissão sale-by-payment-method.viewLimite heavy
Formato
GET /v1/api-public/sales/by-payment-method?from=<início>&to=<fim>&place=<loja>[&event=<evento>]
Parâmetros: Recebimentos por método de pagamento
Parâmetro Obrigatório O que é
from sim início do período, incluído
to sim fim do período, não incluído
place sim o id de uma loja, vindo de /places
event não o id de um evento dessa loja, vindo de /events; sem ele, soma todos os eventos (e as vendas sem evento)

O período pode ter no máximo 120 dias. Com event, não há limite de período: a venda de ingressos abre meses antes do evento, e o evento já restringe a consulta (como no painel). Cada chamada é de uma loja só; para mais de uma, veja Várias lojas.

Antes de usar o número, leia O que entra e o que não entra no total e Vendas que chegam depois.

curl
curl -s "$GOTOTEM_API/v1/api-public/sales/by-payment-method?from=2026-10-01T00:00:00-03:00&to=2026-10-08T00:00:00-03:00&place=L0j4C3ntr0Id00000000000000000000" \
  -H "Authorization: Bearer $GOTOTEM_API_KEY"
PowerShell
$url = "$env:GOTOTEM_API/v1/api-public/sales/by-payment-method" +
    "?from=2026-10-01T00:00:00-03:00&to=2026-10-08T00:00:00-03:00&place=L0j4C3ntr0Id00000000000000000000"
$resumo = Invoke-RestMethod -Uri $url -Headers @{ Authorization = "Bearer $env:GOTOTEM_API_KEY" }

"Total: $($resumo.total.amount) $($resumo.currency)"
$resumo.data | ForEach-Object { "$($_.label): $($_.amount)" }
Python (requests)
import os
from decimal import Decimal

import requests

API = os.environ["GOTOTEM_API"]
HEADERS = {"Authorization": "Bearer " + os.environ["GOTOTEM_API_KEY"]}

params = {
    "from": "2026-10-01T00:00:00-03:00",
    "to": "2026-10-08T00:00:00-03:00",
    "place": "L0j4C3ntr0Id00000000000000000000",
}
resposta = requests.get(API + "/v1/api-public/sales/by-payment-method", headers=HEADERS, params=params, timeout=30)
resposta.raise_for_status()
resumo = resposta.json()

# dinheiro vem como texto: converta para decimal, nunca para ponto flutuante
print("Total:", Decimal(resumo["total"]["amount"]), resumo["currency"])
for linha in resumo["data"]:
    print(linha["label"], Decimal(linha["amount"]))
JavaScript (Node 18+, arquivo .mjs)
const API = process.env.GOTOTEM_API;
const HEADERS = { Authorization: "Bearer " + process.env.GOTOTEM_API_KEY };

const params = new URLSearchParams({
  from: "2026-10-01T00:00:00-03:00",
  to: "2026-10-08T00:00:00-03:00",
  place: "L0j4C3ntr0Id00000000000000000000",
});
const resposta = await fetch(API + "/v1/api-public/sales/by-payment-method?" + params, { headers: HEADERS });
if (!resposta.ok) throw new Error("HTTP " + resposta.status);
const resumo = await resposta.json();

// dinheiro vem como texto ("45678.90"): não use parseFloat para somar valores
console.log("Total:", resumo.total.amount, resumo.currency);
for (const linha of resumo.data) {
  console.log(linha.label, linha.amount);
}
PHP (curl)
<?php
$api = getenv('GOTOTEM_API');
$chave = getenv('GOTOTEM_API_KEY');

$params = [
    'from' => '2026-10-01T00:00:00-03:00',
    'to' => '2026-10-08T00:00:00-03:00',
    'place' => 'L0j4C3ntr0Id00000000000000000000',
];
$ch = curl_init($api . '/v1/api-public/sales/by-payment-method?' . http_build_query($params));
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $chave],
    CURLOPT_TIMEOUT => 30,
]);
$corpo = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) !== 200) {
    throw new RuntimeException('HTTP ' . curl_getinfo($ch, CURLINFO_HTTP_CODE));
}
$resumo = json_decode($corpo, true);

// dinheiro vem como texto ("45678.90"): some com bcadd() ou em centavos, nunca como float
echo 'Total: ', $resumo['total']['amount'], ' ', $resumo['currency'], PHP_EOL;
foreach ($resumo['data'] as $linha) {
    echo $linha['label'], ' ', $linha['amount'], PHP_EOL;
}

As bibliotecas de Python, JavaScript e PHP codificam os parâmetros sozinhas (o : e o + das datas, por exemplo): passe as datas como estão.

Resposta 200
{
  "from": "2026-10-01T03:00:00Z",
  "to": "2026-10-08T03:00:00Z",
  "place": "L0j4C3ntr0Id00000000000000000000",
  "event": null,
  "currency": "BRL",
  "total": { "count": 1234, "amount": "45678.90" },
  "data": [
    {
      "payment_method": "cash",
      "label": "Dinheiro",
      "count": 100,
      "amount": "2500.00",
      "share": "5.473",
      "brands": []
    },
    {
      "payment_method": "tef_credit",
      "label": "TEF - Crédito",
      "count": 500,
      "amount": "25000.00",
      "share": "54.730",
      "brands": [
        { "brand": "MASTERCARD", "count": 300, "amount": "16000.00", "share": "35.027" },
        { "brand": "VISA", "count": 200, "amount": "9000.00", "share": "19.703" }
      ]
    },
    {
      "payment_method": "tef_pix",
      "label": "TEF - Pix",
      "count": 634,
      "amount": "18178.90",
      "share": "39.797",
      "brands": [
        { "brand": null, "count": 634, "amount": "18178.90", "share": "39.797" }
      ]
    }
  ]
}

O que cada campo significa

O que cada campo significa
Campo Significado
from, to o período que foi de fato consultado, em UTC e em segundos inteiros (fração de segundo é arredondada para cima; o limite de 120 dias vale sobre o período já arredondado)
place a loja consultada
event o evento filtrado, ou null quando somou todos os eventos da loja
total.count quantidade de recebimentos no período
total.amount valor recebido no período, sem descontar estornos (veja o que entra e o que não entra)
data[].payment_method código do método (tabela abaixo). Programe contra este campo
data[].label nome do método em português, para exibir. Pode mudar: não compare texto
data[].count, amount quantidade e valor recebidos naquele método
data[].share participação no valor total, em %, com três casas
data[].brands abertura por bandeira do cartão; vazia fora de TEF
data[].brands[].share participação da bandeira no valor total do período (não na linha do método)

Regras que valem a pena conhecer:

  • Dinheiro é texto, com ponto e duas casas ("1234.50"). Converta para decimal, nunca para ponto flutuante.
  • Só aparecem os métodos que tiveram recebimento. Um período sem recebimento devolve "data": [] e total zero. Não conte com uma lista fixa.
  • count conta recebimentos, não pedidos. Um pedido pago metade em dinheiro e metade no cartão aparece uma vez em cada linha.
  • A bandeira vem como o equipamento a gravou, em texto livre: a Gototem não padroniza o nome. Espere grafias diferentes para a mesma bandeira (MASTERCARD, Mastercard, MASTER CREDITO), texto cortado pelo terminal (já vimos 16 caracteres: TICKET RESTAURAN), o nome da adquirente junto (SAFRAPAY MASTER) e vale-refeição dentro do débito (ALELO REFEICAO). "brand": null reúne os recebimentos sem bandeira informada. Para um total por bandeira, monte do seu lado uma tabela de equivalência (por exemplo, "contém MASTER → Mastercard") e mantenha uma categoria "outras" para o que não casar.
  • "brand": "OTHER" reúne as bandeiras gravadas fora do formato. A API só repassa o texto gravado quando ele tem de 1 a 40 caracteres e só letras, dígitos, espaço e os sinais . & + / _ -. O que estiver fora disso (texto longo demais, símbolo estranho) sai somado numa linha só, com "brand": "OTHER". Só o agrupamento muda: quantidade, valor e total continuam os mesmos.
  • As bandeiras vêm do maior valor para o menor (em empate, por nome, com os recebimentos sem bandeira depois).
  • A soma das participações pode não dar exatamente 100.000, por arredondamento. Quando há recebimento de valor negativo no período, uma participação pode ser negativa ou passar de 100.
  • A ordem das linhas é a da tabela de Métodos de pagamento.

O que entra e o que não entra no total

O total é a soma dos recebimentos pagos da loja no período: o dinheiro que entrou, em qualquer método de pagamento.

Entra:

  • recebimento pago de venda e de comanda;
  • recarga e ativação de cartão cashless (é dinheiro que entrou).

Não entra:

  • recebimento não pago ou cancelado;
  • venda de demonstração (a que foi marcada como demonstração, e não como venda real);
  • consumo pago com saldo cashless (esse dinheiro já foi contado na recarga);
  • sangria e suprimento de caixa (são movimentação do caixa, não recebimento);
  • estorno (de item da venda, de produto e de cashless);
  • devolução de saldo cashless (no painel, "Devolver cashless": o saldo que voltou ao cliente em dinheiro). Não é estorno, mas também é dinheiro que saiu, e fica fora pelo mesmo motivo.

Recebimento gravado sem tipo de transação fica fora do total e de todas as linhas, como nos relatórios do painel.

O total é bruto de estornos. Um estorno é registrado à parte, e esta consulta não o soma nem o subtrai: o recebimento original continua no total depois de estornado. Se o seu sistema concilia receita líquida, não use este total sozinho. A API ainda não devolve os estornos; eles precisam vir de outra fonte (peça ao lojista).

Um recebimento pode ter valor negativo. É raro, ele entra na soma como está, e é por isso que amount aceita sinal de menos.

Limitação conhecida do filtro event. Com event, só entra o recebimento que foi gravado com aquele evento. Em casos raros a venda é do evento e o recebimento foi gravado sem ele: esse recebimento fica fora do total com event (e aparece na consulta do mesmo período sem event). O relatório do painel filtra do mesmo jeito, então a API e o painel mostram o mesmo número. Se o total de um evento parecer menor que o esperado, compare com a consulta sem event.

Vendas que chegam depois

A consulta filtra pela hora em que o recebimento aconteceu no equipamento da loja (o totem ou o caixa), e não pela hora em que ele chegou à Gototem. Um equipamento que vendeu sem conexão envia as vendas quando a conexão volta, e elas entram no período em que aconteceram.

Por isso um período que você já consultou pode mudar: o total de ontem pode crescer hoje.

  • O atraso costuma ser de segundos ou minutos. Com o equipamento sem conexão, pode ser de horas e, em casos raros, de dias.
  • Reconsulte os últimos 3 dias a cada atualização (ou ao menos uma vez por dia) e substitua os valores que você guardou. Nunca some uma consulta nova por cima da antiga.
  • Para um fechamento que não pode mudar (o mês, por exemplo), refaça a consulta alguns dias depois do fim do período.

Métodos de pagamento

Métodos de pagamento
payment_method label Linha no relatório do painel
cash Dinheiro Dinheiro
tef_virtual_wallet TEF - Carteira digital TEF - Carteira Virtual
tef_credit TEF - Crédito TEF - Crédito
tef_debit TEF - Débito TEF - Débito
tef_voucher TEF - Voucher TEF - Voucher
tef_voucher_2 TEF - Voucher 2 TEF - Voucher 2
tef_others TEF - Outros TEF - Outros
tef_pix TEF - Pix TEF - Pix
tef_unspecified TEF - Não informado TEF - Não informado
pos POS POS
voucher Voucher Voucher
not_identified Não identificado Não identificado
other Outros Outros

Uma diferença de texto: o mesmo método aparece como "TEF - Carteira digital" na API e "TEF - Carteira Virtual" no painel. O código tef_virtual_wallet é o mesmo.

Novos códigos podem ser acrescentados no futuro. Trate um código desconhecido como "outro método", sem falhar.

Como conferir com o painel

A resposta tem os mesmos números de dois relatórios do painel da Gototem (em Relatórios, no grupo Financeiro): Resumo do recebimento (as linhas de data) e Resumo por bandeira (as brands).

Para reproduzir uma tela do painel, lembre-se de que ele trabalha em horário de Brasília e inclui o último segundo. A tela "01/10/2026 00:00:00 a 07/10/2026 23:59:59" corresponde a:

Parâmetros
from=2026-10-01T00:00:00-03:00
to=2026-10-08T00:00:00-03:00

Ou seja: o mesmo -03:00, e o to um segundo depois do fim que o painel mostra.

Duas diferenças de forma, nas bandeiras:

  • o painel repete uma linha para cada grafia de bandeira com espaços sobrando, e a API as junta;
  • a bandeira gravada fora do formato (mais de 40 caracteres, ou com símbolo além de letras, dígitos, espaço e . & + / _ -) sai na API somada na linha "brand": "OTHER", e no painel com o texto como foi gravado. Quantidade e valor são os mesmos; só o nome da linha muda.

A coluna da direita da tabela de Métodos de pagamento diz qual linha do painel é qual código.

Os números só batem se as duas consultas forem feitas no mesmo momento: uma venda que chega depois (veja Vendas que chegam depois) muda os dois.

Várias lojas

Cada chamada de recebimentos é de uma loja: place é obrigatório e aceita um id só. Lista (place=a,b) ou place ausente respondem 422; loja fora da chave responde 404 com param igual a place, e nada é consultado.

  • Uma loja: uma chamada com o place dela.
  • Total de várias lojas: uma chamada por loja, somando os valores do seu lado. Some os campos amount como decimais e os count como inteiros; não some os share (refaça a participação sobre o total somado).

/places lista também as lojas desativadas ("active": false). Elas continuam consultáveis, para o histórico; se não precisar delas, deixe-as fora do rodízio. Na tela do lojista, só as lojas ativas aparecem para escolha uma a uma: uma loja desativada só entra numa chave marcada com "Todos os estabelecimentos" (que inclui também as lojas criadas depois).

Como o limite desta rota é de 10 chamadas por minuto (veja Limites de uso), não dá para atualizar onze lojas a cada minuto. O que recomendamos: atualize as lojas em rodízio (uma por chamada, uma depois da outra), guardando a última resposta de cada uma.

Quantas chamadas a sua rotina custa. Uma chamada devolve o total do período inteiro (até 120 dias), e não um valor por dia.

  • Total do período: uma chamada por loja. Reconsultar os últimos 3 dias como um período só também é uma chamada por loja.
  • Valor de cada dia: uma chamada por loja e por dia. Três dias em cinco lojas são 15 chamadas: mais de um minuto de cota.
  • Carga inicial: 90 dias, dia a dia, em cinco lojas são 450 chamadas, cerca de 45 minutos no limite da chave. Faça uma vez, em rodízio, e depois mantenha só os últimos dias.

Se as lojas estão em fusos diferentes, lembre-se de que from e to são um instante só para todas: "hoje" em Manaus começa uma hora depois de "hoje" em São Paulo. Para um dia exato por loja, consulte cada loja com o período montado no fuso dela.

Conta e acesso

Quem é esta chave

GET/v1/api-public/pingQualquer chave válidaLimite default

Diz a quem a chave pertence: a credencial, as permissões e a validade dela (expires_at), a conta, os limites em vigor e a hora do servidor em UTC. É a chamada para testar uma chave. superseded vem true quando a chamada usou a chave anterior de uma rotação, que vale só até superseded_until. rate_limit vem null quando nenhum limite está sendo aplicado no momento.

Sem parâmetros.

Resposta 200
{
  "credential": {
    "id": "Cr3d3nc14lId00000000000000000001",
    "name": "Aplicativo de vendas",
    "permissions": [
      "sale-by-payment-method.view",
      "event.view"
    ],
    "expires_at": "2027-10-11T02:59:59Z",
    "superseded": false,
    "superseded_until": null
  },
  "account": {
    "id": "C0nt4Id0000000000000000000000001",
    "name": "Restaurante Exemplo"
  },
  "rate_limit": {
    "default_per_minute": 60,
    "heavy_per_minute": 10
  },
  "server_time": "2026-10-08T17:51:06Z",
  "api_version": "v1"
}

Erros desta rota: 400 conflicting_credentials · 401 invalid_token · 404 not_found · 405 method_not_allowed · 422 invalid_parameter · 429 rate_limited · 500 internal_error

Guia completo desta rota

As lojas da chave

GET/v1/api-public/placesQualquer chave válidaLimite default

Todas as lojas que a chave alcança, de uma vez (sem paginação). O id de cada uma é o que vai no parâmetro place das outras rotas, e timezone é o fuso para montar o dia da loja.

Sem parâmetros.

Resposta 200
{
  "all": false,
  "data": [
    {
      "id": "L0j4C3ntr0Id00000000000000000000",
      "name": "Loja Centro",
      "timezone": "America/Sao_Paulo",
      "active": true
    },
    {
      "id": "L0j4M4n4usId00000000000000000000",
      "name": "Loja Manaus",
      "timezone": "America/Manaus",
      "active": true
    }
  ]
}

Erros desta rota: 400 conflicting_credentials · 401 invalid_token · 404 not_found · 405 method_not_allowed · 422 invalid_parameter · 429 rate_limited · 500 internal_error

Guia completo desta rota

Vendas e recebimentos

Totais de vendas do período

GET/v1/api-public/sales/summaryEm brevePermissão: a definirLimite heavy

Os totais de vendas das lojas no período.

Parâmetros: Totais de vendas do período (referência)
Parâmetro Obrigatório O que é
from sim Início do período, incluído. Data ISO 8601 com fuso. Com to, no máximo 120 dias (sem teto em /sales/by-payment-method com event).
to sim Fim do período, não incluído. Data ISO 8601 com fuso.
place não Um ou mais id de loja, vindos de /places, separados por vírgula (no máximo 100). Sem place, todas as lojas da chave. Loja fora da chave responde 404 com param igual a place.

Em breve: hoje responde 501; os parâmetros acima ainda podem mudar.

Erros desta rota: 400 conflicting_credentials · 401 invalid_token · 404 not_found · 405 method_not_allowed · 422 invalid_parameter · 429 rate_limited · 500 internal_error · 501 not_implemented

Vendas por dia

GET/v1/api-public/sales/by-dayEm brevePermissão: a definirLimite heavy

As vendas do período abertas por dia, cada venda no dia do fuso da loja dela.

Parâmetros: Vendas por dia (referência)
Parâmetro Obrigatório O que é
from sim Início do período, incluído. Data ISO 8601 com fuso. Com to, no máximo 120 dias (sem teto em /sales/by-payment-method com event).
to sim Fim do período, não incluído. Data ISO 8601 com fuso.
place não Um ou mais id de loja, vindos de /places, separados por vírgula (no máximo 100). Sem place, todas as lojas da chave. Loja fora da chave responde 404 com param igual a place.

Em breve: hoje responde 501; os parâmetros acima ainda podem mudar.

Erros desta rota: 400 conflicting_credentials · 401 invalid_token · 404 not_found · 405 method_not_allowed · 422 invalid_parameter · 429 rate_limited · 500 internal_error · 501 not_implemented

Vendas por produto

GET/v1/api-public/sales/by-productEm brevePermissão: a definirLimite heavy

As vendas do período abertas por produto.

Parâmetros: Vendas por produto (referência)
Parâmetro Obrigatório O que é
from sim Início do período, incluído. Data ISO 8601 com fuso. Com to, no máximo 120 dias (sem teto em /sales/by-payment-method com event).
to sim Fim do período, não incluído. Data ISO 8601 com fuso.
place não Um ou mais id de loja, vindos de /places, separados por vírgula (no máximo 100). Sem place, todas as lojas da chave. Loja fora da chave responde 404 com param igual a place.

Em breve: hoje responde 501; os parâmetros acima ainda podem mudar.

Erros desta rota: 400 conflicting_credentials · 401 invalid_token · 404 not_found · 405 method_not_allowed · 422 invalid_parameter · 429 rate_limited · 500 internal_error · 501 not_implemented

Vendas por loja

GET/v1/api-public/sales/by-placeEm brevePermissão: a definirLimite heavy

As vendas do período abertas por loja.

Parâmetros: Vendas por loja (referência)
Parâmetro Obrigatório O que é
from sim Início do período, incluído. Data ISO 8601 com fuso. Com to, no máximo 120 dias (sem teto em /sales/by-payment-method com event).
to sim Fim do período, não incluído. Data ISO 8601 com fuso.
place não Um ou mais id de loja, vindos de /places, separados por vírgula (no máximo 100). Sem place, todas as lojas da chave. Loja fora da chave responde 404 com param igual a place.

Em breve: hoje responde 501; os parâmetros acima ainda podem mudar.

Erros desta rota: 400 conflicting_credentials · 401 invalid_token · 404 not_found · 405 method_not_allowed · 422 invalid_parameter · 429 rate_limited · 500 internal_error · 501 not_implemented

Recebimentos por método de pagamento

GET/v1/api-public/sales/by-payment-methodPermissão sale-by-payment-method.viewLimite heavy

Quanto uma loja recebeu no período, por método de pagamento e com a abertura por bandeira do cartão. Uma loja por chamada; com event, só os recebimentos daquele evento, sem o teto de 120 dias. O total soma os recebimentos pagos e não desconta estornos; ficam fora recebimento não pago ou cancelado, venda de demonstração, consumo pago com saldo cashless, sangria e suprimento. Venda feita sem conexão chega depois e muda um período já consultado: reconsulte os últimos 3 dias. A conta atende duas consultas desta rota ao mesmo tempo: a terceira responde 429 com Retry-After: 5.

Parâmetros: Recebimentos por método de pagamento (referência)
Parâmetro Obrigatório O que é
from sim Início do período, incluído. Data ISO 8601 com fuso. Com to, no máximo 120 dias (sem teto em /sales/by-payment-method com event).
to sim Fim do período, não incluído. Data ISO 8601 com fuso.
place sim O id de uma loja, vindo de /places. Ausente, vazio ou em lista (a,b) responde 422; loja fora da chave (ou que não existe) responde 404 com param igual a place.
event não O id de um evento dessa loja, vindo de /events. Só os recebimentos gravados com esse evento entram, e o teto de 120 dias deixa de valer. Limitação conhecida: recebimento de uma venda do evento que foi gravado sem o evento fica fora. Lista (a,b) responde 422; evento que não existe ou é de outra loja responde 404 com param igual a event.
Resposta 200
{
  "from": "2026-10-01T03:00:00Z",
  "to": "2026-10-08T03:00:00Z",
  "place": "L0j4C3ntr0Id00000000000000000000",
  "event": null,
  "currency": "BRL",
  "total": {
    "count": 1234,
    "amount": "45678.90"
  },
  "data": [
    {
      "payment_method": "cash",
      "label": "Dinheiro",
      "count": 100,
      "amount": "2500.00",
      "share": "5.473",
      "brands": []
    },
    {
      "payment_method": "tef_credit",
      "label": "TEF - Crédito",
      "count": 500,
      "amount": "25000.00",
      "share": "54.730",
      "brands": [
        {
          "brand": "MASTERCARD",
          "count": 300,
          "amount": "16000.00",
          "share": "35.027"
        },
        {
          "brand": "VISA",
          "count": 200,
          "amount": "9000.00",
          "share": "19.703"
        }
      ]
    },
    {
      "payment_method": "tef_pix",
      "label": "TEF - Pix",
      "count": 634,
      "amount": "18178.90",
      "share": "39.797",
      "brands": [
        {
          "brand": null,
          "count": 634,
          "amount": "18178.90",
          "share": "39.797"
        }
      ]
    }
  ]
}

Erros desta rota: 400 conflicting_credentials · 401 invalid_token · 403 forbidden_permission · 404 not_found · 405 method_not_allowed · 422 invalid_parameter · 429 rate_limited · 500 internal_error · 503 temporarily_unavailable

Guia completo desta rota

Pedidos

Pedidos

GET/v1/api-public/ordersEm brevePermissão: a definirLimite default

Os pedidos, cada um como um documento completo, paginados por cursor. Pede ao menos um par de datas: created_from e created_to, ou updated_from e updated_to (no máximo 31 dias cada).

Parâmetros: Pedidos (referência)
Parâmetro Obrigatório O que é
place não Um ou mais id de loja, vindos de /places, separados por vírgula (no máximo 100). Sem place, todas as lojas da chave. Loja fora da chave responde 404 com param igual a place.
created_from não Início da criação do pedido, incluído. Vai junto com created_to.
created_to não Fim da criação do pedido, não incluído.
updated_from não Início da última alteração do pedido, incluído. Vai junto com updated_to.
updated_to não Fim da última alteração do pedido, não incluído.
cursor não O next_cursor da página anterior, da mesma consulta. Em /events, o cursor de outra loja responde 422 com param igual a cursor.
limit não Itens por página, de 1 a 100 (padrão 50). Acima de 100 é recusado.

Em breve: hoje responde 501; os parâmetros acima ainda podem mudar.

Erros desta rota: 400 conflicting_credentials · 401 invalid_token · 404 not_found · 405 method_not_allowed · 422 invalid_parameter · 429 rate_limited · 500 internal_error · 501 not_implemented

Um pedido

GET/v1/api-public/orders/{order_id}Em brevePermissão: a definirLimite default

Um pedido, como um documento completo.

Parâmetros: Um pedido (referência)
Parâmetro Obrigatório O que é
order_id sim O id do pedido, no caminho.

Em breve: hoje responde 501; os parâmetros acima ainda podem mudar.

Erros desta rota: 400 conflicting_credentials · 401 invalid_token · 404 not_found · 405 method_not_allowed · 422 invalid_parameter · 429 rate_limited · 500 internal_error · 501 not_implemented

Cardápio e produtos

Produtos

GET/v1/api-public/productsEm brevePermissão: a definirLimite default

Os produtos das lojas, para traduzir os identificadores que aparecem em vendas e pedidos. Paginados por cursor.

Parâmetros: Produtos (referência)
Parâmetro Obrigatório O que é
place não Um ou mais id de loja, vindos de /places, separados por vírgula (no máximo 100). Sem place, todas as lojas da chave. Loja fora da chave responde 404 com param igual a place.
cursor não O next_cursor da página anterior, da mesma consulta. Em /events, o cursor de outra loja responde 422 com param igual a cursor.
limit não Itens por página, de 1 a 100 (padrão 50). Acima de 100 é recusado.

Em breve: hoje responde 501; os parâmetros acima ainda podem mudar.

Erros desta rota: 400 conflicting_credentials · 401 invalid_token · 404 not_found · 405 method_not_allowed · 422 invalid_parameter · 429 rate_limited · 500 internal_error · 501 not_implemented

Eventos e ingressos

Os eventos de uma loja

GET/v1/api-public/eventsPermissão event.viewLimite default

Os eventos de uma loja da chave, do cadastrado mais recentemente para o mais antigo, paginados por cursor. Eventos desativados também aparecem. O id de cada um é o que vai no parâmetro event dos recebimentos.

Parâmetros: Os eventos de uma loja (referência)
Parâmetro Obrigatório O que é
place sim O id de uma loja, vindo de /places. Ausente, vazio ou em lista (a,b) responde 422; loja fora da chave (ou que não existe) responde 404 com param igual a place.
search não Parte do nome do evento, sem diferenciar maiúsculas nem acentos (1 a 100 caracteres). %, _ e \ são texto comum, não curingas. Caractere de controle ou invisível em qualquer posição responde 422.
cursor não O next_cursor da página anterior, da mesma consulta. Em /events, o cursor de outra loja responde 422 com param igual a cursor.
limit não Itens por página, de 1 a 100 (padrão 50). Acima de 100 é recusado.
Resposta 200
{
  "place": "L0j4C3ntr0Id00000000000000000000",
  "search": "rock",
  "data": [
    {
      "id": "Ev3nt0Id000000000000000000000001",
      "name": "Rock Night",
      "starts_at": "2026-10-09T23:00:00Z",
      "ends_at": "2026-10-10T07:00:00Z",
      "active": true
    }
  ],
  "next_cursor": null
}

Erros desta rota: 400 conflicting_credentials · 401 invalid_token · 403 forbidden_permission · 404 not_found · 405 method_not_allowed · 422 invalid_parameter · 429 rate_limited · 500 internal_error · 503 temporarily_unavailable

Guia completo desta rota

Como pedir ajuda

Fale com a Gototem pelo canal de atendimento que o lojista dono da chave já usa (o mesmo por onde ele trata do painel e das lojas dele). Você pode pedir a ele que abra o chamado com os dados abaixo, ou que inclua você na conversa. A chave é da conta do lojista: por isso o pedido precisa partir dele, ou chegar com ele em cópia.

Dúvidas sobre a integração também chegam por integracao@gototem.com.br: diga o nome da loja e envie os mesmos dados.

Antes de pedir ajuda, confira dois casos que se resolvem sem o suporte:

  • 403 com forbidden_permission: falta uma permissão na chave, e quem a marca é o lojista (veja O 403 diz qual permissão falta);
  • 401 com invalid_token: a chave está errada ou deixou de valer (o campo reason diz se foi revogada, pausada, vencida ou substituída), e quem resolve é o lojista (veja Como saber o estado da chave).

O que enviar ao pedir ajuda

Com estes quatro dados o suporte acha a sua chamada sem precisar de mais nada:

O que enviar ao pedir ajuda
O que Onde está Exemplo
X-Request-Id no cabeçalho de toda resposta da API, inclusive das de erro 7f3a9c0e5b1d4f2a8c6e0b9d7a5c3e1f
A rota chamada o endereço, com os parâmetros GET /v1/api-public/events?place=L0j4C3ntr0Id00000000000000000000
O horário, em UTC a hora da chamada no seu log (diga o fuso, se não for UTC) 2026-10-10T14:32:05Z
O code do erro no corpo da resposta invalid_parameter

Se a resposta veio sem X-Request-Id (veja Erros), envie os outros três e o status HTTP. Num 503, envie também o detail: ele diz qual dos dois casos aconteceu. Envie a rota e o horário mesmo quando tiver o X-Request-Id: uma chamada que deu certo é localizada por eles.

Nunca envie a chave, nem um pedaço dela, nem um print em que ela apareça. O suporte não precisa dela para investigar. Se a chave foi exposta, peça ao lojista para revogá-la.

Criar ou rotacionar uma chave, e mudar as permissões ou as lojas dela, é com o dono da conta, no painel: o atendimento não faz isso.

Antes de atualizar a sua integração, confira no Histórico de mudanças o que mudou desde a última vez.