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:
- 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.
- Clicar em Nova chave.
- Dar um nome que identifique o seu sistema (por exemplo, "Aplicativo de vendas").
- 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).
- Em Estabelecimentos, escolher as lojas que o seu sistema deve enxergar (ou "Todos os estabelecimentos").
- 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(campoexpires_at): combine com o lojista quem avisa e quando ela será estendida. - 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.
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.
- Sem a chave, ou com uma chave que não vale, a resposta é
401comcodeinvalid_token: veja Como saber o estado da chave. - Com uma chave válida que não tem a permissão da rota, a resposta é
403comcodeforbidden_permission: veja O 403 diz qual permissão falta.
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:
export GOTOTEM_API="https://api.gototem.com.br" export GOTOTEM_API_KEY="<a chave que o lojista copiou>"
$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:
Toda chamada leva a chave no cabeçalho Authorization (bearer em minúsculas e espaços a mais
são aceitos):
curl -s "$GOTOTEM_API/v1/api-public/ping" \ -H "Authorization: Bearer $GOTOTEM_API_KEY"
Invoke-RestMethod -Uri "$env:GOTOTEM_API/v1/api-public/ping" `
-Headers @{ Authorization = "Bearer $env:GOTOTEM_API_KEY" }
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())
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
$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'.
{
"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
É a mesma chamada do ping, com outro caminho:
curl -s "$GOTOTEM_API/v1/api-public/places" \ -H "Authorization: Bearer $GOTOTEM_API_KEY"
$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)" }
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"])
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
$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;
}
{
"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âmetroplace;name: o nome para mostrar na sua tela (pode virnull, assim comoactive);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
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â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 -s "$GOTOTEM_API/v1/api-public/events?place=L0j4C3ntr0Id00000000000000000000&search=rock" \ -H "Authorization: Bearer $GOTOTEM_API_KEY"
$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)" }
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"])
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
$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).
{
"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_ateends_atvêm em UTC, comZno final, como toda data da resposta. Para mostrar o horário da loja, converta para otimezonedela: no exemplo,2026-10-09T23:00:00Zé 20:00 em São Paulo.ends_atvemnullquando o evento não tem término válido, estarts_atvemnullquando ele não tem data.- Eventos desativados também aparecem (
"active": false): as vendas de um evento passado continuam consultáveis. next_cursordiferente denullquer dizer que há mais eventos: repita a mesma chamada comcursor=<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,paramigual acursore odetail"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
422com odetail"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
limitpode mudar entre páginas: a leitura continua do evento seguinte ao último que você recebeu, sem repetir nem pular nenhum.
# 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"
$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"
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")
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
$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.
- Baixe a coleção (o arquivo
postman.json). - Postman: clique em Import e escolha o arquivo. Insomnia: use Import e escolha o mesmo arquivo (o Insomnia lê coleções do Postman).
- Abra as variáveis da coleção e preencha
api_keycom a chave que o lojista copiou.base_urljá 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_keyno Base Environment.
- Postman: clique no nome da coleção, na barra lateral, e abra a aba Variables. Escreva
a chave em
- Envie Quem é esta chave: a resposta
200confirma que a chave funciona. Cada requisição traz testes prontos (a aba Test Results do Postman): o status200e a presença dos campos principais da resposta. - Envie As lojas da chave e copie o
idda loja que você quer consultar para a variávelplace_id. As outras requisições usam essa variável no parâmetroplace. - Para filtrar os recebimentos por um evento: envie Os eventos de uma loja, copie o
iddo evento para a variávelevent_ide, em Recebimentos por método de pagamento, marque o parâmetroevent, 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
401comreasonigual arevoked. - 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 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):
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:
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 |
{
"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:
- 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).
- Confira se a chave enviada é a certa e está inteira (sem corte, espaço ou aspas a mais).
- Tente uma vez depois de um minuto.
- Se o
401semreasonpersistir, 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.
| 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:
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:
| 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:
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
| 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:
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
| 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.
# -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"
$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
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"))
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
$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.
{
"code": "invalid_parameter",
"param": "from",
"type": "about:blank",
"title": "Unprocessable Entity",
"status": 422,
"detail": "\"from\" is not a valid date."
}
| 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,
limitfora 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;
fromsemto, outosemfrom: o que faltou;fromdepois deto:from;- período acima do limite:
to; searchvazio, com mais de 100 caracteres ou com caractere de controle ou invisível:search;cursorinválido, ou de outra loja:cursor;- no
404:placequando a chave não alcança a loja (ou ela não existe),eventquando o evento não é daquela loja (ou não existe). Com a loja e o evento errados na mesma chamada, vemplace: a loja é conferida primeiro.
| 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:
GETcom corpo eContent-Typeque não é JSON (um415semcode). Só acontece com uma chave válida (sem ela, a resposta é o401), vem comX-Request-IdeX-RateLimit-*e conta no limite de uso. Não envie corpo nemContent-Type.- Falha inesperada (
5xxsemcode), do servidor que fica à frente da API: pode vir sem oX-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:
{
"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.permissionsdoping. - 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:
# -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"
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"
}
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"),
)
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
$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
v1só 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çalhosDeprecationeSunset. Hoje só existe av1, 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.yamle/v1/api-public/docs/postman.jsonrespondem404, e odetaildo404da 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-method?from=<início>&to=<fim>&place=<loja>[&event=<evento>]
| 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 -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"
$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)" }
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"]))
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
$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.
{
"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
| 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. countconta 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": nullreú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
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:
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
placedela. - Total de várias lojas: uma chamada por loja, somando os valores do seu lado. Some os campos
amountcomo decimais e oscountcomo inteiros; não some osshare(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
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.
{
"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
As lojas da chave
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.
{
"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
Vendas e recebimentos
Totais de vendas do período
Os totais de vendas das lojas no período.
| 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
As vendas do período abertas por dia, cada venda no dia do fuso da loja dela.
| 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
As vendas do período abertas por produto.
| 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
As vendas do período abertas por loja.
| 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
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â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. |
{
"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
Pedidos
Pedidos
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â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
Um pedido, como um documento completo.
| 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
Os produtos das lojas, para traduzir os identificadores que aparecem em vendas e pedidos. Paginados por cursor.
| 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
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â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. |
{
"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
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:
403comforbidden_permission: falta uma permissão na chave, e quem a marca é o lojista (veja O 403 diz qual permissão falta);401cominvalid_token: a chave está errada ou deixou de valer (o camporeasondiz 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 | 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.