# API Gubo · documentação completa > Arquivo único para assistentes de IA. Página para pessoas: https://gubo.com.br/docs-api/ · Contrato: https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/openapi.yaml · Índice: https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/llms.txt > API REST da Gubo (produção sob demanda de vestuário com a marca do cliente). Lê o catálogo da marca, vincula os SKUs da loja às variantes, cota o frete (transportadora exata, mais barata ou mais rápida), cria pedidos (por SKU da variante ou por produto + cor + tamanho), paga com o saldo da carteira, cancela antes do pagamento e acompanha até a entrega por webhooks assinados ou consulta incremental. Base: https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1. Autenticação: `Authorization: Bearer gubo_test_…|gubo_live_…` (a chave decide o modo). Documentação em português. Regras que não podem ser quebradas: - Criar, pagar e cancelar pedidos exigem `Idempotency-Key`; reuse a mesma chave em todas as repetições da mesma operação. Cotação de frete não usa; em `PUT /vinculos` é opcional. - Frete: em `entrega` use `servico` (código de `GET /fretes/servicos`) ou `preferencia` (`mais_barata` | `mais_rapida`), nunca os dois; `se_indisponivel` só com `servico`. - Vínculos: `PUT /vinculos` aceita até 100 itens e devolve um resultado por item; vínculo errado faz o pedido sair com a peça errada, então na dúvida não vincule. - `referencia` é o id do pedido no sistema do cliente, única por modo; `409 referencia_duplicada` traz `erro.detalhes.pedido_id` e deve ser tratado como sucesso. - Dinheiro é inteiro em centavos; os campos terminam em `_centavos`. - Repita só em 429, 5xx, falha de rede e `409 idempotencia_em_andamento`, respeitando `Retry-After`. - Campo desconhecido no corpo gera `422 campo_desconhecido`; ignore campos desconhecidos na resposta; `situacao` desconhecida = em andamento. - Webhooks: HMAC-SHA256 sobre `"."`, cabeçalho `Gubo-Assinatura: t=…,v1=…`, tolerância de 5 min, deduplique por `id`, ordene por `pedido.versao`. - Não existe `POST /pedidos`; use `/pedidos/por-variante` ou `/pedidos/por-produto`. --- # Visão geral A API Gubo deixa o seu sistema cotar o frete, criar pedidos de produção na Gubo, pagar com o saldo da carteira e acompanhar cada pedido até a entrega, sem ninguém digitar nada no Studio. ## O que dá para fazer | Você quer | Use | |---|---| | Saber quais produtos, cores e tamanhos a sua marca tem | `GET /catalogo/variantes` | | Ligar os SKUs da sua loja às variantes da Gubo | `GET` e `PUT /vinculos` ([detalhes](vinculos)) | | Cotar o frete e escolher a transportadora | `POST /fretes/cotacoes` ([detalhes](fretes)) | | Criar um pedido | `POST /pedidos/por-variante` ou `POST /pedidos/por-produto` | | Pagar um pedido | `POST /pedidos/{id}/pagar` | | Consultar o saldo | `GET /carteira` | | Saber quando o pedido mudou | Webhooks ou `GET /pedidos?atualizado_desde=` | | Cancelar antes do pagamento | `POST /pedidos/{id}/cancelar` | A API não cria produtos nem recarrega a carteira. Essas duas coisas continuam no Studio. ## Como funciona ```text Seu sistema ──1. cria o pedido──▶ API Gubo ──2. valida itens, endereço e limites na hora │ ├─3. calcula preço e escolhe o frete (segundos) ├─4. paga com o saldo (automático ou pela API) └─5. produz e envia ──webhooks──▶ Seu sistema ``` ## O básico | | | |---|---| | URL base | `https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1` | | Formato | JSON em UTF-8, só HTTPS | | Autenticação | `Authorization: Bearer ` ([detalhes](autenticacao)) | | Modos | A chave decide: `gubo_test_…` é teste, `gubo_live_…` é produção | | Dinheiro | Inteiro em centavos, campos terminam em `_centavos` | | Repetição segura | Criar, pagar e cancelar exigem `Idempotency-Key` ([detalhes](convencoes#idempotencia)) | | Erros | Sempre `{ "erro": { "codigo", "mensagem", … } }` ([lista](erros)) | ## Como liberar 1. A API está em piloto. A Gubo libera a sua marca na central de pilotos. 2. No Studio, em **Integrações**, a dona da loja clica em **Ativar API** e escolhe como os pedidos serão pagos. 3. O **Gubo Dev** abre em modo teste. Crie uma chave e siga o [Início rápido](inicio-rapido). 4. Com o teste completo, a dona ativa a produção no Gubo Dev. ## Onde está cada coisa - **Esta documentação** fica no Studio e abre sem login. - **O Gubo Dev** (Studio › Integrações › API Gubo) gerencia chaves, webhooks, registro de chamadas e limites. - **Arquivos para máquinas** (`llms.txt`, `llms-full.txt`, `openapi.yaml`) ficam em `https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live`. Veja [Integrar com IA](ia). --- # Início rápido Em 10 minutos você confere a chave, cria um pedido de teste, paga e vê o pedido chegar a `entregue`. Nada disso gera pedido real nem usa saldo real. ## Antes de começar No Gubo Dev, em modo **Teste**, crie uma chave com a permissão **Pedidos completos**. Ela começa com `gubo_test_` e aparece uma única vez. ```bash export GUBO_API_KEY="gubo_test_..." # nunca coloque a chave no código ``` ## 1. Confira a chave ```bash curl https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/conexao \ -H "Authorization: Bearer $GUBO_API_KEY" ``` A resposta traz `"modo": "teste"`, a forma de pagamento da conexão e os seus limites. ## 2. Escolha um SKU ```bash curl "https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/catalogo/variantes?disponivel=true&limite=3" \ -H "Authorization: Bearer $GUBO_API_KEY" ``` ```json { "objeto": "lista", "dados": [ { "objeto": "variante", "sku": "CFT01-PT-M", "disponivel": true, "preco_unitario_centavos": 5990 } ], "tem_mais": true, "proximo_cursor": "eyJrIjoi…" } ``` ## 3. Crie o pedido ```bash curl https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/pedidos/por-variante \ -H "Authorization: Bearer $GUBO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Prefer: wait=10" \ -d '{ "referencia": "TESTE-0001", "itens": [{ "sku": "CFT01-PT-M", "quantidade": 2 }], "destinatario": { "nome": "Ana Souza", "documento": "12345678909", "endereco": { "cep": "01310-100", "logradouro": "Av. Paulista", "numero": "1000", "bairro": "Bela Vista", "cidade": "São Paulo", "uf": "SP" } } }' ``` Resposta `201`. Com `Prefer: wait=10`, o pedido já volta com preço e frete: ```json { "objeto": "pedido", "id": "ped_01JA2X8Q7M3K5V9R2T4W6Y8Z0B", "referencia": "TESTE-0001", "situacao": "aguardando_pagamento", "valores": { "itens_centavos": 11980, "desconto_centavos": 0, "frete_centavos": 2110, "total_centavos": 14090 }, "frete": { "regra": "mais_barata", "prazo_dias_uteis": 14, "simulado": true, "…": "…" } } ``` Sem `entrega`, a Gubo escolhe o frete mais barato. Para escolher a transportadora ou cotar antes, veja [Fretes](fretes). Se a conexão estiver em pagamento automático, a `situacao` já vem `confirmado`. Pule o passo 4. ## 4. Pague ```bash curl https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/pedidos/ped_01JA2X8Q7M3K5V9R2T4W6Y8Z0B/pagar \ -H "Authorization: Bearer $GUBO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: pagar-ped_01JA2X8Q7M3K5V9R2T4W6Y8Z0B" \ -H "Prefer: wait=10" \ -d '{ "valor_esperado_centavos": 13970 }' ``` A `situacao` vira `confirmado`. No teste, o débito sai de uma carteira fictícia de R$ 10.000,00. ## 5. Leve o pedido até a entrega ```bash curl https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/teste/pedidos/ped_01JA2X8Q7M3K5V9R2T4W6Y8Z0B/avancar \ -H "Authorization: Bearer $GUBO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "para": "entregue" }' ``` O pedido passa por `em_producao`, `enviado` e `entregue`. Se você cadastrou um webhook, recebe um evento a cada passo. Tudo aparece no **Registro de chamadas** do Gubo Dev. ## Próximos passos 1. [Criar pedidos](criar-pedido): as duas formas de informar itens e as regras de validação. 2. [Webhooks](webhooks): receba as mudanças e verifique a assinatura. 3. [Ir para produção](producao): checklist e ativação. --- # Autenticação Toda chamada leva uma chave secreta no cabeçalho `Authorization`. ```http Authorization: Bearer gubo_live_4eC39HqLyjWDarjtT1zdp7dc8Mvb2xQa ``` ## Chaves | Prefixo | Modo | Efeito | |---|---|---| | `gubo_test_` | Teste | Pedidos simulados e carteira fictícia ([Modo teste](modo-teste)) | | `gubo_live_` | Produção | Pedidos reais e débito real na carteira | - A URL é a mesma nos dois modos. Quem decide é a chave. Toda resposta traz `Gubo-Modo: teste` ou `Gubo-Modo: producao`. - A chave é **de servidor**. Nunca a coloque em aplicativo, site, front-end ou repositório. - A chave aparece **uma única vez**, na criação. A Gubo guarda só um hash. Perdeu? Crie outra e revogue a antiga. - São no máximo **3 chaves ativas por modo**. Use uma chave por sistema. ## Permissões | Escopo | Permite | |---|---| | `catalogo:ler` | Ler produtos, variantes, cores e tamanhos | | `pedidos:ler` | Consultar e listar pedidos | | `pedidos:criar` | Criar pedidos | | `pedidos:pagar` | Pagar com o saldo da carteira | | `pedidos:cancelar` | Cancelar pedidos não pagos | | `carteira:ler` | Consultar o saldo | | `fretes:cotar` | Cotar o frete e consultar cotações | | `vinculos:ler` | Listar os vínculos de SKU | | `vinculos:escrever` | Gravar e remover vínculos de SKU | No Gubo Dev há quatro atalhos: **Pedidos completos** (todos), **Somente leitura** (`catalogo:ler`, `pedidos:ler`, `carteira:ler`, `vinculos:ler`), **Vínculos** (`catalogo:ler`, `vinculos:ler`, `vinculos:escrever`, ideal para um agente que liga os produtos) e **Personalizado**. `GET /conexao` e `GET /fretes/servicos` funcionam com qualquer escopo. > **Dica:** separe quem cria de quem paga. O ERP cria pedidos com uma chave sem `pedidos:pagar`, e o financeiro paga com outra chave, restrita ao IP do escritório. ## IPs permitidos Opcional. A chave só funciona a partir dos IPs ou faixas informados (IPv4, IPv6 ou CIDR, até 10). Chamadas de outro IP recebem `403 ip_nao_permitido`, com o IP visto pela Gubo em `erro.detalhes.ip`. Recomendado em produção. ## Expiração e rotação - Ao criar a chave, escolha se ela expira: nunca, ou em 30, 90 ou 365 dias. Sete dias antes do vencimento chegam um e-mail e o evento `chave.expirando`. - **Rotacionar** cria uma chave nova com as mesmas permissões e IPs. A antiga continua valendo por 1 h, 24 h ou 7 dias. Troque no seu sistema, confira o "Último uso" e deixe a antiga vencer. - **Revogar** desliga a chave em até 5 segundos. Qualquer pessoa da loja pode revogar. ## Erros de acesso | Status | Código | O que fazer | |---|---|---| | 401 | `chave_ausente` | Envie `Authorization: Bearer …` | | 401 | `chave_invalida` | Confira se copiou a chave inteira | | 401 | `chave_revogada` | Use outra chave | | 401 | `chave_expirada` | Rotacione ou crie outra | | 403 | `escopo_insuficiente` | Use uma chave com o escopo de `erro.detalhes.escopo_necessario` | | 403 | `ip_nao_permitido` | Inclua o IP na chave | | 403 | `producao_nao_ativada` | Ative a produção no Gubo Dev | | 403 | `conexao_bloqueada` | Fale com o suporte | | 429 | `bloqueio_temporario` | Corrija a chave e espere o `Retry-After` | > **Importante:** 10 falhas de autenticação em 10 minutos a partir do mesmo IP bloqueiam esse IP por 15 minutos. Se começar a receber `401`, pare e corrija. Não repita em laço. ## Se a chave vazar 1. Revogue no Gubo Dev. Revogar vem antes de investigar. 2. No **Registro de chamadas**, filtre pela chave e confira IPs e rotas. 3. Crie uma chave nova, com IP restrito. --- # Criar pedidos Há duas rotas, uma para cada forma de identificar os itens. O resto do pedido é igual nas duas. | Rota | Item | Quando usar | |---|---|---| | `POST /pedidos/por-variante` | `{ "sku": "CFT01-PT-M", "quantidade": 2 }` | Seu sistema guarda o SKU de cada variante. **Recomendada** | | `POST /pedidos/por-produto` | `{ "produto": "CFT01", "cor": "PT", "tamanho": "M", "quantidade": 2 }` | Seu sistema guarda produto, cor e tamanho em campos separados | ## Identificar os itens - **SKU da variante:** `--`, como `CFT01-PT-M`. O código do produto é o "SKU do produto" definido em Meus produtos. - **SKU da sua loja:** depois de [vincular](vinculos) a variante, o SKU da sua loja também é aceito. Os dois funcionam. - A comparação ignora maiúsculas, acentos e espaços nas pontas: `cft01-pt-m` encontra `CFT01-PT-M`. - `cor` aceita o código (`PT`) ou o nome (`Preto`). Prefira o código, que nunca muda. - Só são aceitas as **cores liberadas** do produto e as variantes **disponíveis**. - As listas válidas estão em `GET /catalogo/variantes`, `GET /dominios/cores` e `GET /dominios/tamanhos` ([Catálogo](catalogo)). > **Importante:** item que não for reconhecido recusa o pedido inteiro com `422 itens_invalidos`. A Gubo nunca troca um item por outro. Cada problema vem em `erro.detalhes[]`, com sugestões quando houver. ## Exemplo ```bash cURL curl https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/pedidos/por-variante \ -H "Authorization: Bearer $GUBO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 3f1c9a52-6a0e-4c1e-9d7b-2b1f0d6c8e11" \ -H "Prefer: wait=10" \ -d '{ "referencia": "PED-10293", "itens": [{ "sku": "CFT01-PT-M", "quantidade": 2 }], "destinatario": { "nome": "Ana Souza", "documento": "12345678909", "email": "ana@exemplo.com.br", "endereco": { "cep": "01310-100", "logradouro": "Av. Paulista", "numero": "1000", "complemento": "ap 12", "bairro": "Bela Vista", "cidade": "São Paulo", "uf": "SP" } }, "entrega": { "prometida_ate": "2026-10-25" }, "metadados": { "id_erp": "88123" } }' ``` ```js Node.js const resposta = await fetch("https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/pedidos/por-variante", { method: "POST", headers: { Authorization: `Bearer ${process.env.GUBO_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": pedidoLocal.chaveIdempotencia, // gerada e gravada antes Prefer: "wait=10", }, body: JSON.stringify({ referencia: pedidoLocal.numero, itens: pedidoLocal.itens.map((i) => ({ sku: i.sku, quantidade: i.quantidade })), destinatario: pedidoLocal.destinatario, }), }); const pedido = await resposta.json(); ``` ```python Python import os, requests r = requests.post( "https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/pedidos/por-variante", headers={ "Authorization": f"Bearer {os.environ['GUBO_API_KEY']}", "Idempotency-Key": pedido_local.chave_idempotencia, # gerada e gravada antes "Prefer": "wait=10", }, json={ "referencia": pedido_local.numero, "itens": [{"sku": i.sku, "quantidade": i.quantidade} for i in pedido_local.itens], "destinatario": pedido_local.destinatario, }, timeout=30, ) pedido = r.json() ``` ## Tratar a resposta | Resposta | O que fazer | |---|---| | `201` | Guarde `id` (`ped_…`) e `situacao` | | `422 validacao`, `itens_invalidos`, `servico_desconhecido` ou `servico_inativo` | Corrija os dados com base em `erro.detalhes[]` e envie com **nova** `Idempotency-Key` | | `409 referencia_duplicada` | O pedido já existe. Use `erro.detalhes.pedido_id`. Trate como sucesso | | `423 conexao_pausada` | A criação está pausada no Gubo Dev. Não repita até reativar | | `429`, `5xx` ou falha de rede | Repita com a **mesma** `Idempotency-Key`, respeitando `Retry-After` | A regra de ouro: **chave nova só quando o conteúdo mudou.** Repetição por falha técnica usa a mesma chave e nunca cria um segundo pedido. ## Resultado da criação Sem `Prefer: wait`, a resposta volta na hora com `situacao = recebido`, e o preço chega em segundos (webhook ou consulta). Com `Prefer: wait=10`, a API espera até 10 s e normalmente devolve: - `aguardando_pagamento`, quando a conexão paga sob comando ou falta saldo. Veja [Pagar](pagar). - `confirmado`, quando a conexão paga automaticamente. - `pendente`, quando há algo a resolver, como frete indisponível para o CEP ou serviço escolhido indisponível. Veja [Acompanhar](acompanhar). ## Entrega e frete ```json "entrega": { "servico": "jadlog", "se_indisponivel": "mais_barata", "prometida_ate": "2026-10-30" } ``` Escolha **uma** forma: - `servico`: a transportadora exata, por código de `GET /fretes/servicos`. Com `se_indisponivel` você decide o que acontece se ela não atender o CEP: `pendencia` (padrão), `mais_barata` ou `mais_rapida`. - `preferencia`: `mais_barata` ou `mais_rapida`. A Gubo calcula todas as opções e devolve a escolhida. - Nenhuma das duas: com `prometida_ate`, o frete mais barato que entrega até a data, já contando 7 dias úteis de produção. Sem data, o mais barato. O frete escolhido volta em `frete`, com `servico_codigo`, `regra`, prazo e `previsao_entrega`. Para mostrar preço e prazo ao seu cliente antes de criar o pedido, use a [cotação de frete](fretes). > **Importante:** com `servico` e sem `se_indisponivel`, um serviço que não atende o CEP deixa o pedido `pendente` com `servico_indisponivel`. Nada é cobrado. Cancele e crie outro, ou peça ajuste à Gubo. ## Boas práticas - `referencia` é o número do pedido no seu sistema. É única por modo, para sempre. - Use `metadados` para guardar ids do seu sistema (até 20 chaves). Não coloque dados pessoais ali. - Sincronize o catálogo uma vez por dia e confira os SKUs antes de enviar. Sem isso também funciona: a criação diz exatamente qual item falhou. - Os campos completos estão na [referência de Pedidos](pedidos#post-pedidos-por-variante). --- # Fretes Cote o frete antes de criar o pedido e escolha a transportadora: um serviço exato, o mais barato ou o mais rápido. Os preços já são os **preços finais da Gubo**, iguais aos cobrados no pedido. | Rota | Escopo | Para quê | |---|---|---| | `GET /fretes/servicos` | qualquer | Lista os serviços de transporte (códigos para `entrega.servico`) | | `POST /fretes/cotacoes` | `fretes:cotar` | Cota o frete para um CEP e uma lista de itens | | `GET /fretes/cotacoes/{id}` | `fretes:cotar` | Consulta uma cotação | ## Como o frete é escolhido O bloco `entrega` é o mesmo na cotação e no pedido. A cotação mostra a escolha que o pedido faria. Use **um** destes caminhos: | Você envia | A Gubo escolhe | `frete.regra` | |---|---|---| | `"servico": "jadlog"` | Exatamente esse serviço | `servico_definido` | | `"preferencia": "mais_barata"` | O menor preço | `mais_barata` | | `"preferencia": "mais_rapida"` | O menor prazo | `mais_rapida` | | `"prometida_ate": "2026-10-30"` | O mais barato que entrega até a data. Se nenhum chega, o mais rápido | `mais_barata_no_prazo` ou `prazo_em_risco` | | nada | O mais barato | `mais_barata` | - Empates: no mais barato vence o menor prazo. No mais rápido vence o menor preço. - `servico` e `preferencia` não andam juntos (`422 validacao`, código `conflito`). Para dizer o que fazer se o serviço não estiver disponível, use `se_indisponivel`. - `prometida_ate` pode acompanhar qualquer caminho. Com `servico` ou `preferencia`, ela só marca `prazo_em_risco`. ### Quando o serviço escolhido não atende o CEP Nem todo serviço atende todo CEP ou todo pacote. `se_indisponivel` decide o que acontece: | `se_indisponivel` | Resultado | |---|---| | `pendencia` (padrão) | O pedido fica `pendente` com a pendência `servico_indisponivel`. Nada é cobrado | | `mais_barata` | A Gubo usa o mais barato. `frete.regra` = `mais_barata` | | `mais_rapida` | A Gubo usa o mais rápido. `frete.regra` = `mais_rapida` | Para saber se houve troca, compare `frete.servico_codigo` com o `entrega.servico` que você enviou. ```json "entrega": { "servico": "correios-sedex", "se_indisponivel": "mais_barata" } ``` ## Prazos `prazo_total_dias_uteis` = **7 dias úteis de produção** + o prazo da transportadora. A `previsao_entrega` conta a partir de hoje, em dias úteis, no fuso de São Paulo. No pedido, a previsão conta a partir do pagamento. ### GET /fretes/servicos Os serviços que a Gubo usa hoje. Guarde os códigos. Não é preciso consultar a cada pedido. ```json { "objeto": "lista", "dados": [ { "objeto": "servico_frete", "codigo": "correios", "nome": "Olist Envios Correios PAC", "transportadora": "Correios", "modalidade": "PAC", "ativo": true }, { "objeto": "servico_frete", "codigo": "jadlog", "nome": "Olist Envios Jadlog", "transportadora": "Jadlog", "modalidade": "Normal", "ativo": true }, { "objeto": "servico_frete", "codigo": "pax", "nome": "Olist Envios Pax", "transportadora": "Pax", "modalidade": "Expresso", "ativo": true } ], "tem_mais": false, "proximo_cursor": null } ``` - Hoje: `correios`, `correios-sedex`, `jadlog`, `jet`, `loggi`, `pax`, `gfl` e `imile`. A lista pode crescer. Use sempre o que a rota devolver. - Serviço com `ativo: false` foi desativado pela Gubo. Enviá-lo devolve `422 servico_inativo`. - Código que não existe devolve `422 servico_desconhecido`, com sugestões em `erro.detalhes.sugestoes`. - No modo teste, sem serviços reais publicados, aparecem os serviços de exemplo `gubo-teste-economico`, `gubo-teste-padrao` e `gubo-teste-expresso`. ### POST /fretes/cotacoes | Campo | Tipo | Descrição | |---|---|---| | `cep` | texto | CEP de destino, com ou sem hífen. **Obrigatório** | | `itens` | lista | 1 a 50 itens, no mesmo formato da criação de pedidos: todos por `sku` ou todos por `produto` + `cor` + `tamanho`. **Obrigatório** | | `entrega` | objeto | `servico`, `preferencia`, `se_indisponivel` e `prometida_ate`, como acima. Opcional | ```bash cURL curl https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/fretes/cotacoes \ -H "Authorization: Bearer $GUBO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "cep": "01310-100", "itens": [{ "sku": "CFT01-PT-M", "quantidade": 2 }], "entrega": { "preferencia": "mais_rapida" } }' ``` ```js Node.js const r = await fetch("https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/fretes/cotacoes", { method: "POST", headers: { Authorization: `Bearer ${process.env.GUBO_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ cep: "01310-100", itens: [{ sku: "CFT01-PT-M", quantidade: 2 }], entrega: { preferencia: "mais_rapida" } }), }); let cotacao = await r.json(); while (cotacao.situacao === "pendente") { await new Promise((ok) => setTimeout(ok, 2000)); cotacao = await (await fetch(`https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/fretes/cotacoes/${cotacao.id}`, { headers: { Authorization: `Bearer ${process.env.GUBO_API_KEY}` }, })).json(); } ``` ```python Python import os, time, requests h = {"Authorization": f"Bearer {os.environ['GUBO_API_KEY']}"} cotacao = requests.post("https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/fretes/cotacoes", headers=h, timeout=30, json={ "cep": "01310-100", "itens": [{"sku": "CFT01-PT-M", "quantidade": 2}], "entrega": {"preferencia": "mais_rapida"}, }).json() while cotacao["situacao"] == "pendente": time.sleep(2) cotacao = requests.get(f"https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/fretes/cotacoes/{cotacao['id']}", headers=h, timeout=30).json() ``` Resposta `201`: ```json { "objeto": "cotacao_frete", "id": "cot_01JA2M7Q…", "situacao": "pronta", "cep": "01310-100", "simulada": false, "valida_ate": "2026-10-11T15:42:10Z", "pacote": { "pecas": 2, "peso_g": 380, "valor_declarado_centavos": 11980 }, "itens": [{ "sku": "CFT01-PT-M", "produto": "CFT01", "cor": "PT", "tamanho": "M", "quantidade": 2 }], "entrega": { "preferencia": "mais_rapida" }, "opcoes": [ { "servico": "pax", "nome": "Olist Envios Pax", "transportadora": "Pax", "modalidade": "Expresso", "preco_centavos": 1282, "prazo_transporte_dias_uteis": 1, "prazo_producao_dias_uteis": 7, "prazo_total_dias_uteis": 8, "previsao_entrega": "2026-10-21", "dentro_do_prazo": null }, { "servico": "jadlog", "nome": "Olist Envios Jadlog", "transportadora": "Jadlog", "modalidade": "Normal", "preco_centavos": 1776, "prazo_transporte_dias_uteis": 5, "prazo_producao_dias_uteis": 7, "prazo_total_dias_uteis": 12, "previsao_entrega": "2026-10-27", "dentro_do_prazo": null } ], "recomendacoes": { "mais_barata": "pax", "mais_rapida": "pax", "mais_barata_no_prazo": null }, "escolha": { "servico": "pax", "regra": "mais_rapida", "prazo_em_risco": false, "preco_centavos": 1282, "previsao_entrega": "2026-10-21" }, "sem_escolha": null, "erro": null, "criado_em": "2026-10-11T15:12:10Z" } ``` | Campo | Descrição | |---|---| | `situacao` | `pronta`, `pendente` (ainda calculando), `falhou` ou `expirada` | | `valida_ate` | 30 minutos após o cálculo | | `opcoes[]` | Todas as opções disponíveis, com preço final e prazos | | `recomendacoes` | O serviço mais barato, o mais rápido e o mais barato que cumpre `prometida_ate` | | `escolha` | O que um pedido com esta mesma `entrega` usaria agora | | `sem_escolha` | Quando não há escolha: `{ codigo: "frete_indisponivel" \| "servico_indisponivel", mensagem }` | | `dentro_do_prazo` | Só com `prometida_ate`: `true` se a previsão cumpre a data | | `simulada` | `true` no modo teste com CEP de cenário | **Respostas:** | Status | Quando | |---|---| | `201` | Cotação pronta (normalmente em 2 a 6 segundos) | | `202` | Ainda calculando depois de 20 s. Consulte `Location` respeitando `Retry-After` | | `422` | `validacao`, `itens_invalidos`, `itens_misturados`, `servico_desconhecido`, `servico_inativo` ou `limite_itens_pedido` | | `429` | `limite_requisicoes` ou `limite_cotacoes_diario` | | `503 cotacao_indisponivel` | A transportadora não respondeu. Tente de novo após `Retry-After` | > **Dica:** a mesma loja, CEP e pacote (peças, peso e valor) reaproveitam a cotação válida mais recente. A resposta chega na hora e **não conta** no limite diário. Você não precisa guardar cache do seu lado. ### GET /fretes/cotacoes/{id} Devolve a cotação no mesmo formato. Aceita `Prefer: wait=N` (até 10 s) enquanto estiver `pendente`. Cotação de outra conexão ou de outro modo: `404 cotacao_nao_encontrada`. ## Cotação e pedido O pedido usa o mesmo bloco `entrega`. Envie no pedido o que você enviou na cotação: ```json "entrega": { "servico": "jadlog", "se_indisponivel": "mais_barata" } ``` - **Produção:** a cotação mostra o preço e o prazo para o seu cliente. O pedido cota de novo quando é processado e aplica a mesma regra. O valor costuma ser o mesmo. O que vale é o `frete` do pedido, antes do pagamento. - **Teste:** se houver cotação válida para o mesmo CEP e os mesmos itens, o pedido usa exatamente as opções dela. - O pedido criado traz o frete escolhido: ```json "frete": { "servico": "Olist Envios Jadlog", "servico_codigo": "jadlog", "transportadora": "Jadlog", "prazo_dias_uteis": 12, "previsao_entrega": "2026-10-27", "regra": "servico_definido", "prazo_em_risco": false, "simulado": false } ``` Enquanto o frete não é escolhido, `frete` e `valores` são `null`. Veja [Pedidos](pedidos#objeto-pedido). ## Limites | | Produção | Teste | |---|---|---| | Cotações por minuto, por chave | 30 | 30 | | Cotações calculadas por dia, por conexão | 1.000 | 300 | | Itens por cotação | 50 | 50 | | Validade | 30 min | 30 min | Cotações reaproveitadas e simuladas não contam no limite diário. O consumo do dia está em `GET /conexao` (`consumo_hoje.cotacoes`). ## Modo teste - CEP comum: a cotação é real, com as transportadoras da Gubo. Nada é cobrado. - CEP de cenário: cotação simulada na hora. `00000-100` devolve `opcoes: []` e `sem_escolha.codigo = frete_indisponivel`. --- # Vínculos de SKU Ligue cada produto da sua loja virtual à variante da Gubo que ele representa. Depois do vínculo, você cria pedidos com o **SKU da sua loja**, sem tabela de conversão. | Rota | Escopo | Para quê | |---|---|---| | `GET /vinculos` | `vinculos:ler` | Lista as variantes da marca com o SKU vinculado de cada uma | | `PUT /vinculos` | `vinculos:escrever` | Grava, troca ou remove vínculos, até 100 por chamada | - A **variante** é sempre identificada pelo SKU padrão da Gubo: `--`, como `CFT01-PT-M`. - O **SKU vinculado** é o da sua loja, como `CAMISETA-FOLHAS-PRETA-M`. É o mesmo "SKU de integração" que aparece em Meus produtos. - Cada variante tem no máximo um SKU vinculado. Cada SKU vinculado aponta para uma única variante. - O SKU padrão continua funcionando nos pedidos depois do vínculo. > **Dica:** a chave com o atalho **Vínculos** (`catalogo:ler`, `vinculos:ler`, `vinculos:escrever`) é a ideal para um agente de IA: ela não cria nem paga pedidos. ### GET /vinculos | Parâmetro | Descrição | |---|---| | `situacao` | `sem_vinculo`, `vinculado`, `pendente` ou `falhou` | | `produto` | Código do produto (`CFT01`) | | `cor` | Código ou nome da cor | | `tamanho` | Código do tamanho | | `disponivel` | `true` ou `false` | | `busca` | Parte do código do produto, do SKU ou do nome do produto | | `limite`, `cursor` | [Paginação](convencoes#paginacao). `limite` de 1 a 100, padrão 20 | ```bash cURL curl "https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/vinculos?situacao=sem_vinculo&limite=100" \ -H "Authorization: Bearer $GUBO_API_KEY" ``` ```json { "objeto": "lista", "dados": [ { "objeto": "vinculo", "variante": "CFT01-PT-M", "produto": { "codigo": "CFT01", "nome": "Camiseta Folhas Tropicais" }, "cor": { "codigo": "PT", "nome": "Preto" }, "tamanho": "M", "disponivel": true, "sku": null, "situacao": "sem_vinculo", "pendente": null, "erro": null, "atualizado_em": null } ], "tem_mais": true, "proximo_cursor": "eyJ2IjoiQ0ZUMDEtUFQtTSJ9" } ``` | `situacao` | Significado | |---|---| | `sem_vinculo` | Só o SKU padrão vale. `sku` é `null` | | `vinculado` | `sku` é o SKU da loja, já em uso nos pedidos | | `pendente` | Gravação em andamento. O pedido em `pendente.sku` entra em segundos | | `falhou` | A última gravação não entrou. O motivo está em `erro` e o vínculo anterior continua valendo | A ordem é estável (pelo SKU padrão). Você pode parar e continuar depois pelo cursor. ### PUT /vinculos ```json { "simular": false, "itens": [ { "variante": "CFT01-PT-M", "sku": "CAMISETA-FOLHAS-PRETA-M" }, { "variante": "CFT01-PT-G", "sku": "CAMISETA-FOLHAS-PRETA-G" }, { "variante": "CFT01-OW-P", "sku": null } ] } ``` | Campo | Descrição | |---|---| | `itens` | 1 a 100 itens. **Obrigatório** | | `itens[].variante` | SKU padrão da variante. Também aceita o SKU já vinculado | | `itens[].sku` | SKU da loja: 1 a 60 caracteres entre letras, números e `. _ - /`. `null` ou `""` **remove** o vínculo | | `simular` | `true` valida tudo e não grava nada. Padrão `false` | ```bash cURL curl -X PUT https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/vinculos \ -H "Authorization: Bearer $GUBO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "itens": [{ "variante": "CFT01-PT-M", "sku": "CAMISETA-FOLHAS-PRETA-M" }] }' ``` ```js Node.js const r = await fetch("https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/vinculos", { method: "PUT", headers: { Authorization: `Bearer ${process.env.GUBO_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ itens: pares.map((p) => ({ variante: p.varianteGubo, sku: p.skuLoja })) }), }); const lote = await r.json(); const rejeitados = lote.resultados.filter((x) => x.resultado === "rejeitado"); ``` ```python Python import os, requests r = requests.put( "https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/vinculos", headers={"Authorization": f"Bearer {os.environ['GUBO_API_KEY']}"}, json={"itens": [{"variante": p["variante"], "sku": p["sku_loja"]} for p in pares]}, timeout=30, ) rejeitados = [x for x in r.json()["resultados"] if x["resultado"] == "rejeitado"] ``` Resposta `200`, sempre com um resultado por item, na ordem enviada: ```json { "objeto": "lote_vinculos", "simulacao": false, "sandbox": false, "resumo": { "recebidos": 3, "aceitos": 1, "inalterados": 1, "rejeitados": 1 }, "resultados": [ { "indice": 0, "variante": "CFT01-PT-M", "sku": "CAMISETA-FOLHAS-PRETA-M", "resultado": "aceito", "situacao": "pendente", "erro": null }, { "indice": 1, "variante": "CFT01-PT-G", "sku": "CAMISETA-FOLHAS-PRETA-G", "resultado": "inalterado", "situacao": "vinculado", "erro": null }, { "indice": 2, "variante": "CFT01-OW-P", "sku": null, "resultado": "rejeitado", "situacao": null, "erro": { "codigo": "variante_desconhecida", "mensagem": "A variante não existe no catálogo da marca. Consulte GET /v1/vinculos.", "sugestoes": ["CFT01-OF-P"] } } ] } ``` | `resultado` | Significado | |---|---| | `aceito` | Gravado. Em produção fica `pendente` por alguns segundos. No teste já vale | | `valido` | Só com `simular: true`: seria aceito | | `inalterado` | Já estava assim. Não conta no limite | | `rejeitado` | Nada foi gravado para este item. Veja `erro.codigo` | Um item rejeitado **não** impede os outros. Corrija só os rejeitados e envie de novo. | `erro.codigo` | O que fazer | |---|---| | `item_invalido` | Envie só `variante` (texto) e `sku` (texto ou `null`) | | `variante_desconhecida` | Use um `variante` de `GET /vinculos`. Veja `erro.sugestoes` | | `variante_ambigua` | Envie o SKU padrão, não o SKU vinculado | | `sku_invalido` | Ajuste o SKU ao formato | | `duplicado_no_lote` | A variante apareceu duas vezes. `erro.item` indica a primeira | | `sku_duplicado_no_lote` | O mesmo SKU foi pedido para duas variantes. `erro.item` indica a primeira | | `sku_em_uso` | O SKU já é de outra variante (`erro.variante_em_uso`). Remova lá antes, ou troque as duas em chamadas separadas | | `variante_invalida` | A variante deixou de existir na Gubo (só em `situacao: falhou`) | | `erro_gubo` | A Gubo não conseguiu gravar (só em `situacao: falhou`). Envie de novo | Erros do lote inteiro: `422 validacao` (corpo, mais de 100 itens, `simular` não booleano), `403 escopo_insuficiente`, `429 limite_requisicoes` e `429 limite_vinculos_diario`. **Repetição:** o `PUT` é idempotente pelo conteúdo. Repetir o mesmo lote devolve `inalterado` e não grava de novo. `Idempotency-Key` é opcional. Se enviada, a repetição devolve a mesma resposta. ## Produção e teste | | Produção | Teste | |---|---|---| | Onde vale | Na sua marca, inclusive no Studio e nas outras integrações | Só nas chaves de teste | | Tempo | `pendente` por alguns segundos. Remoção pode levar até 30 s | Na hora | | Catálogo | `GET /catalogo/variantes` mostra o SKU depois de gravado | Mostra o SKU do teste na hora | Use o modo teste para ensaiar o mapeamento inteiro sem mexer na loja. Os vínculos de teste são apagados por **Limpar dados de teste** no Gubo Dev. ## Limites | | Limite | |---|---| | `PUT /vinculos` por minuto, por chave | 20 (até 2.000 variantes por minuto) | | `GET /vinculos` por minuto, por chave | 300 (o mesmo das leituras) | | Itens por chamada | 100 | | Vínculos aceitos por dia, por conexão | 5.000 | Só `aceito` conta no limite diário. `inalterado`, `rejeitado` e simulações não contam. O consumo está em `GET /conexao` (`consumo_hoje.vinculos`). ## Vincular com um agente de IA Um agente pode ligar centenas de produtos em minutos. O roteiro seguro: 1. `GET /vinculos?situacao=sem_vinculo&limite=100`, página a página. 2. Para cada variante, procure no catálogo da loja o produto com o mesmo modelo, cor e tamanho. Cor e tamanho chegam com código e nome. 3. Na dúvida, **não vincule**. Liste para uma pessoa conferir. 4. `PUT /vinculos` com `"simular": true` e o lote de até 100 pares. 5. Corrija os `rejeitados` e envie o lote real, sem `simular`. 6. `GET /vinculos?situacao=pendente` até esvaziar, depois `?situacao=falhou` para conferir. > **Importante:** um vínculo errado faz o pedido sair com a peça errada. Teste o roteiro com a chave de teste e revise uma amostra antes de rodar em produção. O prompt pronto para o agente está em [Integrar com IA](ia#vincular-produtos). --- # Pagar Todo pedido é pago com o **saldo da carteira** da marca. A recarga é feita no Studio, por Pix ou cartão. ## Forma de pagamento da conexão A dona da loja escolhe no Gubo Dev, em **Configurações**. `GET /conexao` mostra a forma atual em `pagamento`. | `pagamento` | O que acontece | |---|---| | `automatico` | Assim que o pedido tem preço, a Gubo debita o saldo e o pedido vai para `confirmado`. Sem saldo, ele para em `aguardando_pagamento` | | `aguardar` | O pedido sempre para em `aguardando_pagamento`. Você confere o total e paga pela API, ou alguém paga no Studio | O seu código deve funcionar nas duas formas: trate `aguardando_pagamento` e `confirmado` como resultados normais da criação. ## Pagar pela API ```bash curl https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/pedidos/$ID/pagar \ -H "Authorization: Bearer $GUBO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: pagar-$ID" \ -H "Prefer: wait=10" \ -d '{ "valor_esperado_centavos": 13970 }' ``` - **`valor_esperado_centavos`** (recomendado): se o total mudou, a Gubo recusa com `409 valor_divergente` em vez de cobrar. - **`Idempotency-Key: pagar-`**: duas tentativas de pagar o mesmo pedido nunca cobram duas vezes. - Resposta `202`. Com `Prefer: wait`, normalmente já volta `pagamento.situacao = pago`. Sem ele, volta `processando`. O pagamento termina quando chega `pedido.confirmado` (deu certo) ou `pagamento.recusado` (não deu, com o motivo em `pagamento.motivo_recusa`). ## Quando falta saldo ```json { "erro": { "codigo": "saldo_insuficiente", "mensagem": "Saldo insuficiente para pagar este pedido. Faltam R$ 42,30.", "detalhes": { "saldo_disponivel_centavos": 100000, "total_centavos": 104230, "falta_centavos": 4230 } } } ``` Recarregue no Studio e chame de novo com a **mesma** `Idempotency-Key`. Um `402` não fica guardado pela idempotência, então a nova chamada executa de verdade. ## Consultar o saldo ```bash curl https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/carteira -H "Authorization: Bearer $GUBO_API_KEY" ``` ```json { "objeto": "carteira", "moeda": "BRL", "saldo_disponivel_centavos": 184250, "saldo_reservado_centavos": 14330, "atualizado_em": "2026-10-11T14:03:05Z" } ``` `saldo_reservado_centavos` é o valor de pagamentos em andamento. Consulte antes de um lote grande. Para ser avisado, configure o alerta de saldo no Gubo Dev e receba o evento `carteira.saldo_baixo`. ## Limite de valor por dia A conexão tem um teto de valor pago por dia (padrão R$ 5.000). Ele conta só os pagamentos feitos pela API (automáticos ou por `/pagar`). Ao estourar, a resposta é `429 limite_valor_diario`, com `erro.detalhes.disponivel_hoje_centavos`. A dona pode reduzir o teto no Gubo Dev e pedir aumento à Gubo. --- # Acompanhar pedidos ## Ciclo de vida ```text recebido ─┬─▶ aguardando_pagamento ──▶ confirmado ──▶ em_producao ──▶ enviado ──▶ entregue ├─▶ confirmado (pagamento automático) ├─▶ pendente ──▶ aguardando_pagamento └─▶ recusado cancelado: a partir de recebido, pendente ou aguardando_pagamento ``` | `situacao` | Significa | O que você faz | |---|---|---| | `recebido` | A Gubo está calculando preço e frete | Espere alguns segundos | | `pendente` | Algo impede seguir. Veja `pendencias[]` | Nada, a Gubo tenta resolver. Ou cancele | | `aguardando_pagamento` | Tem preço. `valores` preenchido | [Pague](pagar) | | `confirmado` | Pago | Nada | | `em_producao` | Em produção | Nada | | `enviado` | Postado. `envio.rastreio` preenchido | Avise o seu cliente | | `entregue` | Entregue | Fim | | `cancelado` | Cancelado por você ou no Studio | Fim | | `recusado` | A Gubo não conseguiu processar. Veja `motivo_recusa` | Corrija e crie de novo com **outra** `referencia` | > **Importante:** novas situações podem surgir. Trate qualquer valor desconhecido como "em andamento". Nunca quebre. **Pendências** (`pendencias[].codigo`): | Código | Significado | |---|---| | `frete_indisponivel` | Nenhuma transportadora atende o CEP e os itens | | `servico_indisponivel` | O `entrega.servico` escolhido não atende o CEP, e `se_indisponivel` era `pendencia`. Veja [Fretes](fretes#quando-o-servico-escolhido-nao-atende-o-cep) | | `endereco_invalido` | O endereço precisa de ajuste | | `envio_indisponivel` | A etiqueta não pôde ser gerada | | `item_indisponivel` | Um item deixou de estar disponível | | `revisao_gubo` | A equipe da Gubo está revisando o pedido | Enquanto o frete não é escolhido, `valores` e `frete` são `null`. ## Webhook ou consulta Use os dois: o webhook traz a mudança na hora, e a consulta periódica cobre qualquer evento perdido. 1. **Webhooks** (recomendado): cadastre uma URL no Gubo Dev. Veja [Webhooks](webhooks). 2. **Consulta incremental:** guarde o maior `atualizado_em` que você já viu e, a cada 5 minutos, chame: ```bash curl "https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/pedidos?atualizado_desde=2026-10-11T14:00:00Z&limite=100" \ -H "Authorization: Bearer $GUBO_API_KEY" ``` Com `atualizado_desde`, a lista vem em ordem crescente de `atualizado_em`. Percorra as páginas até `tem_mais = false`. Não consulte pedido por pedido em laço. ## Descartar mudanças antigas Cada pedido tem `versao`, que cresce a cada mudança. Se a versão que você já tem é maior ou igual à recebida, ignore a mudança. Isso resolve eventos fora de ordem e repetidos. ## Encontrar um pedido - Pelo id: `GET /pedidos/{id}`. - Pela sua referência: `GET /pedidos?referencia=PED-10293`. - Toda resposta e todo webhook trazem `referencia` e `metadados`. ## Cancelar ```bash curl https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/pedidos/$ID/cancelar \ -H "Authorization: Bearer $GUBO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: cancelar-$ID" \ -d '{ "motivo": "Cliente desistiu" }' ``` Só é possível cancelar em `recebido`, `pendente` e `aguardando_pagamento`. Depois do pagamento, a resposta é `409 pedido_nao_cancelavel`, e o caminho é o suporte. Cancelar um pedido já cancelado responde `200`. --- # Webhooks A cada mudança relevante, a Gubo faz um `POST` HTTPS para a URL que você cadastrar no Gubo Dev, na aba **Webhooks**. ## Cadastrar 1. No Gubo Dev, escolha o modo (Teste ou Produção), abra **Webhooks** e clique em **Adicionar endpoint**. 2. Informe a URL (só `https://`) e os eventos. 3. Copie o **segredo de assinatura** (`whsec_…`). 4. Clique em **Enviar teste**. A Gubo envia um `ping` e mostra o resultado na hora. Cada modo aceita até 3 endpoints. A URL não pode apontar para IP privado, `localhost` nem domínio da Gubo. ## Eventos | Tipo | Quando | |---|---| | `pedido.pendente` | O pedido entrou em `pendente` ou mudou de pendência | | `pedido.aguardando_pagamento` | O pedido tem preço. `valores` preenchido | | `pedido.confirmado` | Pago | | `pedido.em_producao` | Entrou em produção | | `pedido.enviado` | Postado. `envio.rastreio` preenchido | | `pedido.entregue` | Entregue | | `pedido.cancelado` | Cancelado pela API ou no Studio | | `pedido.recusado` | Recusado. Veja `motivo_recusa` | | `pagamento.recusado` | O pagamento falhou. O pedido continua `aguardando_pagamento` | | `carteira.saldo_baixo` | O saldo ficou abaixo do alerta. No máximo um por dia | | `conexao.pausada` / `conexao.reativada` | A criação de pedidos foi pausada ou reativada | | `chave.expirando` | Uma chave expira em 7 dias | | `ping` | Envio de teste do painel | > **Importante:** novos tipos podem surgir. Responda `2xx` a qualquer tipo que você não conheça. ## Formato ```http POST /webhooks/gubo HTTP/1.1 Content-Type: application/json Gubo-Evento-Id: evt_01JA2Z4N8P2Q6R0S4T8V2X6Z0C Gubo-Evento-Tipo: pedido.confirmado Gubo-Assinatura: t=1760191385,v1=5f8a0c3e1b… ``` ```json { "id": "evt_01JA2Z4N8P2Q6R0S4T8V2X6Z0C", "objeto": "evento", "tipo": "pedido.confirmado", "modo": "producao", "criado_em": "2026-10-11T14:03:05Z", "dados": { "pedido": { "objeto": "pedido", "id": "ped_01JA2X8Q7M…", "referencia": "PED-10293", "situacao": "confirmado", "versao": 4 } } } ``` Eventos de pedido trazem o **objeto Pedido completo**. `carteira.saldo_baixo` traz `dados.carteira`, `conexao.*` traz `dados.conexao` e `chave.expirando` traz `dados.chave`. ## Responder - Responda **`2xx` em até 10 segundos**. Outro status, redirecionamento ou tempo esgotado conta como falha. - Grave o evento numa fila sua, responda `200` e processe depois. ## Garantias - **Pelo menos uma vez:** o mesmo evento pode chegar duas vezes. Deduplique pelo `id`. - **Sem ordem garantida:** use `dados.pedido.versao` e ignore versões que você já tem. - Na dúvida, `GET /pedidos/{id}` sempre traz o estado atual. ```text ao receber evento: assinatura inválida → responda 400 já processou evento.id → responda 200 versao_local(pedido.id) >= pedido.versao → registre o id e responda 200 atualize o pedido local e registre o id → responda 200 ``` ## Novas tentativas Depois de uma falha, a Gubo tenta de novo em **1 min, 5 min, 30 min, 2 h, 6 h, 12 h e 24 h**, e depois uma vez por dia, até completar 3 dias. Se tudo falhar, o endpoint é **desativado** e a dona da conexão recebe e-mail. - **Testar e reativar**, no endpoint, envia um `ping` e reativa o endpoint se o seu servidor responder `2xx`. - **Pausar** suspende o envio durante uma manutenção. Nada fica guardado para depois. - Em ambos os casos, recupere o período com `GET /pedidos?atualizado_desde=`, ou reenvie entregas uma a uma no painel. ## Verificar a assinatura O cabeçalho `Gubo-Assinatura` tem o formato `t=,v1=`. 1. Separe `t` e todos os `v1`. Durante a troca do segredo chegam dois `v1`. 2. Monte `"."`, usando o corpo exatamente como recebido, antes de qualquer parse. 3. Calcule HMAC-SHA256 com o segredo `whsec_…` inteiro, em hexadecimal minúsculo. 4. Compare com cada `v1` em tempo constante. Basta um bater. 5. Recuse se `t` estiver a mais de 5 minutos do seu relógio. ```js Node.js import crypto from "node:crypto"; import express from "express"; const SEGREDO = process.env.GUBO_WEBHOOK_SECRET; const app = express(); function assinaturaValida(corpoBruto, cabecalho, segredo, toleranciaSeg = 300) { if (!cabecalho) return false; const partes = cabecalho.split(",").map((p) => p.split("=")); const t = Number(partes.find(([k]) => k === "t")?.[1]); const assinaturas = partes.filter(([k]) => k === "v1").map(([, v]) => v); if (!t || assinaturas.length === 0) return false; if (Math.abs(Date.now() / 1000 - t) > toleranciaSeg) return false; const esperado = Buffer.from( crypto.createHmac("sha256", segredo).update(`${t}.${corpoBruto}`).digest("hex"), "hex"); return assinaturas.some((v) => { const recebido = Buffer.from(v, "hex"); return recebido.length === esperado.length && crypto.timingSafeEqual(esperado, recebido); }); } app.post("/webhooks/gubo", express.raw({ type: "application/json" }), async (req, res) => { const corpo = req.body.toString("utf8"); if (!assinaturaValida(corpo, req.get("Gubo-Assinatura"), SEGREDO)) return res.sendStatus(400); await fila.adicionar(JSON.parse(corpo)); // processe fora da requisição res.sendStatus(200); }); ``` ```python Python import hmac, hashlib, os, time from flask import Flask, request, abort SEGREDO = os.environ["GUBO_WEBHOOK_SECRET"] app = Flask(__name__) def assinatura_valida(corpo: bytes, cabecalho: str, segredo: str, tolerancia: int = 300) -> bool: if not cabecalho: return False partes = [p.split("=", 1) for p in cabecalho.split(",")] t = next((v for k, v in partes if k == "t"), None) assinaturas = [v for k, v in partes if k == "v1"] if not t or not assinaturas or abs(time.time() - int(t)) > tolerancia: return False esperado = hmac.new(segredo.encode(), f"{t}.".encode() + corpo, hashlib.sha256).hexdigest() return any(hmac.compare_digest(esperado, v) for v in assinaturas) @app.post("/webhooks/gubo") def webhook(): corpo = request.get_data() # bytes brutos if not assinatura_valida(corpo, request.headers.get("Gubo-Assinatura"), SEGREDO): abort(400) fila.adicionar(request.get_json()) return "", 200 ``` ```php PHP $tolerancia) return false; $esperado = hash_hmac('sha256', $t . '.' . $corpo, $segredo); foreach ($assinaturas as $v) { if (hash_equals($esperado, $v)) return true; } return false; } if (!assinaturaValida($corpo, $cabecalho, $segredo)) { http_response_code(400); exit; } // grave json_decode($corpo, true) numa fila e processe depois http_response_code(200); ``` | Sintoma | Causa | |---|---| | A assinatura nunca bate | O framework fez parse e o JSON foi reserializado. Use o corpo bruto | | Bate em teste e falha em produção | Cada modo e cada endpoint têm o seu segredo | | Falha só às vezes | Relógio do servidor fora de hora. Sincronize por NTP | ## Trocar o segredo No endpoint, clique em **Trocar segredo**. Durante 24 horas, cada evento leva dois `v1`, um com o segredo antigo e outro com o novo. Atualize o seu sistema nesse intervalo. > **Dica:** não confie no IP de origem. A Gubo não garante IPs fixos, e a assinatura é a garantia. --- # Modo teste Com uma chave `gubo_test_`, você exercita a integração inteira sem gerar pedido real nem mexer no saldo real. ## O que é real e o que é simulado | Item | No modo teste | |---|---| | Catálogo, preços e validações | **Reais**: os mesmos da sua marca | | Idempotência, `referencia` e limites por minuto | Reais. O limite diário é de 1.000 pedidos | | Cotação de frete | **Real**, com as transportadoras e os preços da Gubo. CEP de cenário: simulada | | Frete do pedido | O da cotação válida com o mesmo CEP e itens. Sem cotação: **simulado** por tabela (`frete.simulado = true`) | | Desconto do plano | **Não** é aplicado. `valores.desconto_centavos` é sempre `0` | | Vínculos de SKU | Valem na hora, **só** para as chaves de teste. A loja real não muda | | Carteira | **Fictícia**, começa com R$ 10.000,00 | | Produção e envio | **Simulados**: só avançam quando você pede | | Webhooks | Reais, para os endpoints do modo teste | | Studio | Pedidos de teste **não** aparecem no Studio | ## Frete simulado Sem cotação válida, o pedido de teste usa até 4 serviços com valores fixos. A regra de escolha (`servico`, `preferencia`, `prometida_ate`) é a mesma da produção. | Região do CEP | Serviço mais barato | Prazo de transporte | |---|---|---| | Sudeste (CEP 01 a 39) | R$ 19,90 | 7 dias úteis | | Sul (CEP 80 a 99) | R$ 22,90 | 8 dias úteis | | Demais regiões | R$ 29,90 | 11 dias úteis | Cada serviço seguinte custa R$ 8,00 a mais e chega 2 dias úteis antes. Cada peça além da primeira soma R$ 1,20. > **Dica:** para testar com preços reais, cote antes com `POST /fretes/cotacoes` e crie o pedido com o mesmo CEP e os mesmos itens em até 30 minutos. ## Avançar o pedido Depois de pago, o pedido de teste fica em `confirmado` até você pedir: ```bash curl https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/teste/pedidos/$ID/avancar \ -H "Authorization: Bearer $GUBO_API_KEY" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" -d '{ "para": "entregue" }' ``` Ele passa por `em_producao`, `enviado` (rastreio fictício `TESTE` + 9 dígitos) e `entregue`. Cada passo dispara o webhook correspondente. `para` aceita `em_producao`, `enviado` ou `entregue`. ## CEPs de cenário Use estes CEPs para provocar situações difíceis. O resto do endereço pode ser qualquer um válido. | CEP | Resultado | |---|---| | `00000-100` | A cotação volta sem opções e o pedido fica `pendente` com `frete_indisponivel` | | `00000-200` | O pagamento é recusado: `pagamento.recusado` com `saldo_insuficiente` | | `00000-300` | O pedido é `recusado` com `item_indisponivel` | | `00000-400` | O processamento demora 20 segundos. Teste o seu tempo limite e o `Prefer: wait` | ## Carteira de teste ```bash # zera a carteira para provocar 402 saldo_insuficiente curl https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/teste/carteira \ -H "Authorization: Bearer $GUBO_API_KEY" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" -d '{ "saldo_centavos": 0 }' ``` ## Provoque antes de ir para produção - [ ] Um SKU errado: `422 itens_invalidos`. - [ ] A mesma `referencia` duas vezes: `409 referencia_duplicada`. - [ ] A mesma chamada com a mesma `Idempotency-Key`: a mesma resposta, sem pedido novo. - [ ] A carteira zerada: `402 saldo_insuficiente`. - [ ] Os quatro CEPs de cenário. - [ ] Um `entrega.servico` que não atende o CEP, com e sem `se_indisponivel`. - [ ] Um lote de vínculos com `simular: true` e um SKU repetido: `sku_duplicado_no_lote`. ## Limpar Em **Configurações**, **Apagar dados de teste** remove pedidos, cotações, vínculos, eventos e registro do modo teste e devolve a carteira a R$ 10.000,00. Chaves e endpoints continuam. --- # Ir para produção A produção é ativada no Gubo Dev pela dona da loja, depois de uma lista curta de verificações. A URL não muda: troque a chave `gubo_test_` por uma `gubo_live_`. ## Lista de ativação O Gubo Dev confere sozinho os quatro primeiros itens: | Item | Como cumprir | |---|---| | Pedido de teste criado | Qualquer `201` em `POST /pedidos/por-*` com chave de teste | | Pedido de teste pago | Um pedido de teste em `confirmado` | | Acompanhamento definido | Um webhook de teste respondeu `2xx`, **ou** a dona confirmou que vai acompanhar por consulta | | Carteira com saldo | Saldo real acima de zero | | Limites revisados | A dona confirma os limites diários de produção no painel | ## Antes de ativar - [ ] A chave fica no servidor, em variável de ambiente ou cofre de segredos. - [ ] Toda criação grava a `Idempotency-Key` antes de chamar e a reaproveita nas repetições. - [ ] `referencia` é o número do pedido no seu sistema. - [ ] O código trata `aguardando_pagamento` e `confirmado` como resultados normais da criação. - [ ] Repete só `429`, `5xx`, `409 idempotencia_em_andamento` e falhas de rede, respeitando `Retry-After`. - [ ] O webhook verifica a assinatura, deduplica pelo `id` e usa `versao`. - [ ] Há uma consulta periódica com `atualizado_desde` cobrindo eventos perdidos. - [ ] Situações desconhecidas são tratadas como "em andamento". - [ ] Os logs guardam o `Gubo-Request-Id` de cada chamada, para o suporte. ## Ao ativar 1. Crie a chave de produção, com IPs permitidos sempre que possível. 2. Cadastre o webhook de produção. O segredo é diferente do de teste. 3. Faça um primeiro pedido real pequeno e acompanhe pelo **Registro de chamadas**. ## Botão de emergência **Pausar criação**, no topo do Gubo Dev, faz a API recusar novos pedidos com `423 conexao_pausada`. Consultas e pagamentos continuam funcionando. Use quando o seu sistema estiver enviando algo errado. --- # Referência da API **Base:** `https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1` · **Formato:** JSON UTF-8 · **Autenticação:** `Authorization: Bearer ` O contrato de máquina completo está em [`openapi.yaml`](https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/openapi.yaml). Importe no Postman, Insomnia ou no gerador de cliente da sua linguagem. | Método e rota | Escopo | Resumo | |---|---|---| | [`GET /conexao`](conexao#get-conexao) | qualquer | Configuração, limites e consumo da conexão | | [`GET /catalogo/produtos`](catalogo#get-catalogo-produtos) | `catalogo:ler` | Lista os produtos da marca | | [`GET /catalogo/produtos/{codigo}`](catalogo#get-catalogo-produtos-codigo) | `catalogo:ler` | Um produto, com as variantes | | [`GET /catalogo/variantes`](catalogo#get-catalogo-variantes) | `catalogo:ler` | Lista as variantes (SKUs) | | [`GET /dominios/cores`](catalogo#get-dominios-cores) | `catalogo:ler` | Lista as cores | | [`GET /dominios/tamanhos`](catalogo#get-dominios-tamanhos) | `catalogo:ler` | Lista os tamanhos | | [`POST /pedidos/por-variante`](pedidos#post-pedidos-por-variante) | `pedidos:criar` | Cria um pedido pelo SKU da variante | | [`POST /pedidos/por-produto`](pedidos#post-pedidos-por-produto) | `pedidos:criar` | Cria um pedido por produto, cor e tamanho | | [`GET /pedidos`](pedidos#get-pedidos) | `pedidos:ler` | Lista pedidos | | [`GET /pedidos/{id}`](pedidos#get-pedidos-id) | `pedidos:ler` | Consulta um pedido | | [`POST /pedidos/{id}/pagar`](pedidos#post-pedidos-id-pagar) | `pedidos:pagar` | Paga com o saldo da carteira | | [`POST /pedidos/{id}/cancelar`](pedidos#post-pedidos-id-cancelar) | `pedidos:cancelar` | Cancela um pedido não pago | | [`GET /carteira`](carteira#get-carteira) | `carteira:ler` | Consulta o saldo | | [`GET /fretes/servicos`](fretes#get-fretes-servicos) | qualquer | Lista os serviços de transporte | | [`POST /fretes/cotacoes`](fretes#post-fretes-cotacoes) | `fretes:cotar` | Cota o frete e mostra a escolha | | [`GET /fretes/cotacoes/{id}`](fretes#get-fretes-cotacoes-id) | `fretes:cotar` | Consulta uma cotação | | [`GET /vinculos`](vinculos#get-vinculos) | `vinculos:ler` | Lista variantes com o SKU vinculado | | [`PUT /vinculos`](vinculos#put-vinculos) | `vinculos:escrever` | Grava ou remove vínculos de SKU, até 100 por chamada | | [`POST /teste/pedidos/{id}/avancar`](teste#post-teste-pedidos-id-avancar) | qualquer, só teste | Avança um pedido de teste | | [`POST /teste/carteira`](teste#post-teste-carteira) | qualquer, só teste | Define o saldo da carteira de teste | Regras que valem para todas as rotas: [Convenções](convencoes). Todos os códigos de erro: [Erros](erros). --- # Convenções Regras que valem para todas as rotas. ## Identificadores | Prefixo | Objeto | |---|---| | `ped_` | Pedido. Opaco e permanente. Use para consultar, pagar e cancelar | | `evt_` | Evento de webhook | | `req_` | Requisição. Vem no cabeçalho `Gubo-Request-Id` e no corpo dos erros | - `referencia` é o **seu** identificador do pedido: até 64 caracteres entre letras, números, `-`, `_`, `.`, `/` e `#`. Única por modo, para sempre. - `numero` é o número do pedido no Studio. Pode vir `null` enquanto o pedido está `recebido`. ## Dinheiro Todo valor é um **inteiro em centavos de real**, num campo terminado em `_centavos`. R$ 139,70 é `13970`. Nunca use ponto flutuante para dinheiro. ## Datas - Data e hora: ISO 8601 em UTC, como `2026-10-11T14:03:22Z`. - Só data: `AAAA-MM-DD`, no fuso de São Paulo. - Os limites diários viram à meia-noite de São Paulo. ## Paginação ```http GET /v1/pedidos?limite=50 GET /v1/pedidos?limite=50&cursor=eyJ0IjoxNzI4… ``` ```json { "objeto": "lista", "dados": [], "tem_mais": true, "proximo_cursor": "eyJ0IjoxNzI4…" } ``` `limite` vai de 1 a 100 (padrão 20). Continue enquanto `tem_mais` for `true`. O cursor é opaco: não o monte nem o decodifique. ## Metadados Pedidos aceitam `metadados`: até 20 pares de texto (chave até 40 caracteres, valor até 500). A Gubo devolve como recebeu, em toda resposta e webhook. Não coloque dados pessoais. ## Cabeçalhos | Cabeçalho | Direção | Uso | |---|---|---| | `Authorization: Bearer …` | Envio | A chave | | `Content-Type: application/json` | Envio | Obrigatório em `POST` | | `Idempotency-Key` | Envio | Obrigatório ao criar, pagar e cancelar pedidos. Opcional em `PUT /vinculos`. Não usado em `POST /fretes/cotacoes` | | `Prefer: wait=N` | Envio | Em criação e pagamento: espera até N segundos (1 a 15) pelo resultado | | `Gubo-Request-Id` | Resposta | Id da requisição. Guarde nos seus logs | | `Gubo-Modo` | Resposta | `teste` ou `producao` | | `Gubo-Idempotencia: repetida` | Resposta | A resposta repete uma chamada anterior | | `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` | Resposta | Limite por minuto da rota: a barreira mais apertada entre chave e loja | | `Retry-After` | Resposta | Em `429`, `503` e `409 idempotencia_em_andamento`: segundos para tentar de novo | ## Idempotência Repetir uma chamada **nunca** cria um segundo pedido nem cobra duas vezes. - Criar, pagar e cancelar pedidos exigem `Idempotency-Key`, com 1 a 255 caracteres. Sem ele: `428 idempotencia_obrigatoria`. - `POST /fretes/cotacoes` não usa a chave: cotar não muda nada e cotações iguais são reaproveitadas. - `PUT /vinculos` já é idempotente pelo conteúdo. A chave é opcional. - A Gubo guarda a resposta da chave por **24 horas**, separada por conexão e modo. - Mesma chave e mesmo corpo: a mesma resposta, com `Gubo-Idempotencia: repetida`. - Mesma chave e corpo diferente: `409 idempotencia_conflito`. - Primeira chamada ainda em andamento: `409 idempotencia_em_andamento` com `Retry-After: 1`. - `402`, `409 pagamento_em_andamento`, `429` e `5xx` **não** ficam guardados, porque nada aconteceu. Repetir depois deles executa de novo, com segurança. | Chamada | Chave recomendada | |---|---| | Criar pedido | Um UUID gerado e **gravado** com o pedido no seu banco, antes da chamada | | Pagar | `pagar-` | | Cancelar | `cancelar-` | A `referencia` única completa a proteção: ela vale para sempre e evita o mesmo pedido enviado por dois caminhos (dois processos, uma reimportação). Nesse caso a resposta é `409 referencia_duplicada`, com `erro.detalhes.pedido_id`. ## Limites Cada chamada passa por três barreiras, nesta ordem: o **IP** de origem, a **chave** e a **loja** (todas as chaves do mesmo modo somadas). A barreira da loja só conta o que passou pela da chave, então uma chave descontrolada não esgota as outras. | Por minuto | Por chave | Por loja | |---|---|---| | Criar pedido | 30 | 60 | | Pagar e cancelar | 30 | 60 | | Cotar frete (`POST /fretes/cotacoes`) | 30 | 60 | | Gravar vínculos (`PUT /vinculos`, até 100 itens cada) | 20 | 40 | | Leituras (`GET`) | 300 | 600 | | Por conexão, por dia | Produção | Teste | |---|---|---| | Pedidos criados | 200 | 1.000 | | Valor pago pela API | R$ 5.000 | sem limite | | Itens por pedido | 100 | 100 | | Cotações de frete calculadas | 1.000 | 300 | | Itens por cotação | 50 | 50 | | Vínculos de SKU aceitos | 5.000 | 5.000 | | Por IP de origem | Regra | |---|---| | Requisições | 600 por minuto | | Escritas (`POST` e `PUT`) | 120 por minuto | | Falhas de autenticação | 10 em 10 minutos bloqueiam o IP por 15 minutos | | Tamanho do corpo | 64 KB | - Em `429 limite_requisicoes`, `erro.detalhes.barreira` diz qual barreira parou a chamada (`ip`, `chave` ou `loja`) e `Retry-After` diz quando tentar de novo. - `GET /conexao` mostra os limites das três barreiras e o consumo do dia. - A dona da loja pode reduzir os limites diários no Gubo Dev e pedir aumento à Gubo. - Para importar muitos pedidos, mande em série ou no máximo 3 em paralelo, respeitando `RateLimit-Remaining`. ## Versões A versão está na URL (`/v1`). Dentro dela, a Gubo só **acrescenta**: campos, rotas, eventos e valores novos. Ignore campos que você não conhece. Mudanças que quebram o contrato vão para `/v2`, com pelo menos 12 meses de convivência. Veja o [Changelog](changelog). --- # Erros Todo erro tem o mesmo envelope. Decida pelo `erro.codigo`, nunca pela `mensagem`. ```json { "erro": { "codigo": "saldo_insuficiente", "mensagem": "Saldo insuficiente para pagar este pedido. Faltam R$ 42,30.", "campo": null, "detalhes": { "saldo_disponivel_centavos": 100000, "total_centavos": 104230, "falta_centavos": 4230 }, "documentacao": "https://gubo.com.br/docs-api/erros#saldo_insuficiente" }, "request_id": "req_01JA2Y0C4F8G…" } ``` - `codigo` é estável. A `mensagem` é para pessoas e pode mudar. - `detalhes` é uma lista (erros de campo e de item) ou um objeto (dados do erro). - `documentacao` leva direto à linha do código nesta página. **Repetir?** diz se vale repetir **a mesma chamada, com a mesma `Idempotency-Key`**. ## Requisição | Status | Código | Significa | O que fazer | Repetir? | |---|---|---|---|---| | 400 | `requisicao_invalida` | JSON mal formado | Corrija o JSON | Não | | 400 | `parametro_invalido` | Parâmetro de consulta inválido. `campo` diz qual | Corrija | Não | | 404 | `rota_inexistente` | A rota não existe na v1 | Confira o caminho e o `/v1` | Não | | 405 | `metodo_nao_permitido` | Método errado para a rota | Confira a [referência](referencia) | Não | | 413 | `corpo_muito_grande` | Corpo maior que 64 KB | Divida o pedido | Não | | 415 | `tipo_conteudo_invalido` | Falta `Content-Type: application/json` | Envie o cabeçalho | Não | | 428 | `idempotencia_obrigatoria` | Criação, pagamento ou cancelamento sem `Idempotency-Key` | Envie o cabeçalho | Não | ## Acesso | Status | Código | Significa | O que fazer | Repetir? | |---|---|---|---|---| | 401 | `chave_ausente` | Sem `Authorization: Bearer` | Envie a chave | Não | | 401 | `chave_invalida` | Chave inexistente ou incompleta | Confira se copiou inteira | Não | | 401 | `chave_revogada` | Revogada no painel | Use outra chave | Não | | 401 | `chave_expirada` | Passou da data | Rotacione ou crie outra | Não | | 403 | `escopo_insuficiente` | Falta permissão. `detalhes.escopo_necessario` | Use uma chave com o escopo | Não | | 403 | `ip_nao_permitido` | IP fora da lista. `detalhes.ip` | Inclua o IP na chave | Não | | 403 | `acesso_nao_liberado` | A API não está liberada para a marca | Fale com a Gubo | Não | | 403 | `conexao_bloqueada` | Bloqueada pela Gubo | Fale com o suporte | Não | | 403 | `producao_nao_ativada` | A produção não foi ativada | Ative no Gubo Dev | Não | | 403 | `modo_incompativel` | Rota `/teste/*` com chave de produção | Use chave de teste | Não | | 404 | `pedido_nao_encontrado` | Não existe neste modo e nesta conexão | Confira se não é um id de teste em produção | Não | | 404 | `produto_nao_encontrado` | Código de produto inexistente | Confira o catálogo | Não | | 404 | `cotacao_nao_encontrada` | A cotação não existe nesta conexão e neste modo | Faça uma nova cotação | Não | ## Estado | Status | Código | Significa | O que fazer | Repetir? | |---|---|---|---|---| | 402 | `saldo_insuficiente` | Falta saldo. `detalhes.falta_centavos` | Recarregue no Studio | Depois de recarregar | | 409 | `referencia_duplicada` | A referência já existe. `detalhes.pedido_id` | Use o pedido existente | Não | | 409 | `idempotencia_conflito` | Mesma chave com corpo diferente | Gere chave nova para conteúdo novo | Não | | 409 | `idempotencia_em_andamento` | A primeira chamada ainda está em andamento | Espere o `Retry-After` | **Sim** | | 409 | `pedido_nao_pagavel` | Não está `aguardando_pagamento`. `detalhes.situacao` | Consulte o pedido | Não | | 409 | `pagamento_em_andamento` | Já há um pagamento processando | Espere o webhook ou consulte | Não | | 409 | `valor_divergente` | O total mudou. `detalhes.total_centavos` | Revise e pague com o novo valor | Não | | 409 | `pedido_nao_cancelavel` | Já pago ou adiante | Fale com o suporte | Não | | 409 | `avanco_invalido` | Só teste: avanço antes de `confirmado` ou para trás | Pague o pedido de teste antes | Não | | 423 | `conexao_pausada` | Criação pausada no Gubo Dev | Reative no painel | Depois de reativar | ## Frete | Status | Código | Significa | O que fazer | Repetir? | |---|---|---|---|---| | 422 | `servico_desconhecido` | `entrega.servico` não existe. `detalhes.sugestoes` | Use um código de `GET /fretes/servicos` | Não | | 422 | `servico_inativo` | O serviço foi desativado pela Gubo | Escolha outro serviço | Não | | 422 | `itens_misturados` | Itens por `sku` e por `produto` no mesmo corpo | Use uma forma só | Não | | 503 | `cotacao_indisponivel` | A transportadora não respondeu. `detalhes.cotacao_id` | Espere o `Retry-After` e cote de novo | **Sim** | Os erros por item de `PUT /vinculos` não são status HTTP: chegam em `resultados[].erro`. Veja [Vínculos](vinculos#put-vinculos). ## Validação `422` com `erro.codigo` igual a `validacao` (campos) ou `itens_invalidos` (itens). Cada problema vem em `erro.detalhes[]`, com `campo`, `codigo` e `mensagem`. **Todos os problemas vêm de uma vez.** Corrija e envie com **nova** `Idempotency-Key`. | Código em `detalhes[]` | Significa | |---|---| | `campo_obrigatorio` | Faltou o campo | | `campo_desconhecido` | O campo não existe no contrato, como um erro de digitação | | `tipo_invalido` | Tipo errado, como texto no lugar de número | | `formato_invalido` | Formato ou tamanho fora da regra | | `documento_invalido` | CPF ou CNPJ com dígito verificador inválido | | `cep_invalido` | CEP sem 8 dígitos | | `uf_invalida` | UF inexistente | | `data_invalida` | `prometida_ate` no passado ou depois de 120 dias | | `sku_desconhecido` | O SKU não existe na marca. Pode trazer `sugestoes` | | `produto_desconhecido` | O produto não existe. Pode trazer `sugestoes` | | `cor_desconhecida` | A cor não existe | | `cor_nao_liberada` | A cor não está liberada para o produto. Traz `cores_liberadas` | | `tamanho_indisponivel` | O tamanho não existe para o modelo. Traz `tamanhos` | | `variante_indisponivel` | A variante está indisponível. Traz `motivo` | | `item_duplicado` | O mesmo SKU aparece duas vezes. Some as quantidades | | `quantidade_invalida` | Fora de 1 a 100 | | `limite_itens_pedido` | Mais itens que o limite da conexão | | `conflito` | Campos que não andam juntos, como `entrega.servico` com `entrega.preferencia` | ## Limites | Status | Código | Significa | O que fazer | Repetir? | |---|---|---|---|---| | 429 | `limite_requisicoes` | Limite por minuto do IP, da chave ou da loja. `detalhes.barreira` e `detalhes.limite_por_minuto` | Espere o `Retry-After` | **Sim** | | 429 | `limite_pedidos_diario` | Teto diário de pedidos | Espere o dia virar ou ajuste o limite | No dia seguinte | | 429 | `limite_valor_diario` | Teto diário de valor pago. `detalhes.disponivel_hoje_centavos` | Ajuste o limite ou espere | No dia seguinte | | 429 | `limite_cotacoes_diario` | Teto diário de cotações calculadas | Reaproveite cotações recentes | No dia seguinte | | 429 | `limite_vinculos_diario` | Teto diário de vínculos aceitos. `detalhes.usado_hoje` | Envie o restante amanhã | No dia seguinte | | 429 | `bloqueio_temporario` | IP bloqueado por falhas de autenticação | Corrija a chave e espere | Depois do prazo | ## Falhas da Gubo | Status | Código | O que fazer | Repetir? | |---|---|---|---| | 500 | `erro_interno` | Repita com espera crescente. Se persistir, envie o `request_id` ao suporte | **Sim** | | 503 | `indisponivel_temporariamente` | Manutenção ou instabilidade momentânea. Espere o `Retry-After` e repita com a mesma `Idempotency-Key` | **Sim** | ## Motivos no pedido Não são status HTTP: chegam no objeto Pedido e nos webhooks. | Onde | Código | Significa | |---|---|---| | `pendencias[]` | `frete_indisponivel` | Nenhuma transportadora atende agora. A Gubo tenta de novo | | `pendencias[]` | `servico_indisponivel` | O serviço de `entrega.servico` não atende o CEP | | `pendencias[]` | `endereco_invalido` | A transportadora recusou o endereço | | `pendencias[]` | `envio_indisponivel` | Não foi possível gerar o envio | | `motivo_recusa` | `item_indisponivel` | Um item ficou indisponível depois da criação | | `motivo_recusa` | `processamento_falhou` | Falha de processamento. O suporte é avisado | | `pagamento.motivo_recusa` | `saldo_insuficiente` | O saldo mudou entre o pedido de pagamento e o débito | | `pagamento.motivo_recusa` | `conexao_bloqueada` | A conexão foi bloqueada no meio do pagamento | --- # Conexão A conexão liga a sua loja à API. Cada loja tem uma, com configurações separadas para teste e produção. ### GET /conexao Funciona com qualquer escopo. Use para verificar a chave e para mostrar limites no seu sistema. ```bash curl https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1/conexao -H "Authorization: Bearer $GUBO_API_KEY" ``` ```json { "objeto": "conexao", "id": "con_01J9ZK2W4X…", "loja": { "nome": "Folhas Tropicais" }, "modo": "producao", "situacao": "ativa", "pagamento": "aguardar", "chave": { "nome": "ERP Bling", "prefixo": "gubo_live_4eC3", "escopos": ["pedidos:criar", "pedidos:ler", "catalogo:ler", "fretes:cotar"], "expira_em": null }, "limites": { "pedidos_por_dia": 200, "valor_pago_por_dia_centavos": 500000, "itens_por_pedido": 100, "criar_por_minuto": 30, "pagar_por_minuto": 30, "leituras_por_minuto": 300, "cotar_por_minuto": 30, "vincular_por_minuto": 20, "cotacoes_por_dia": 1000, "vinculos_por_dia": 5000, "loja_por_minuto": { "criar": 60, "pagar": 60, "leituras": 600, "cotar": 60, "vincular": 40 }, "ip_por_minuto": { "chamadas": 600, "escritas": 120 } }, "consumo_hoje": { "pedidos": 12, "valor_pago_centavos": 81240, "cotacoes": 37, "vinculos": 0 } } ``` | Campo | Descrição | |---|---| | `modo` | `teste` ou `producao`, conforme a chave | | `situacao` | `ativa` ou `pausada`. Pausada: a criação de pedidos está suspensa no Gubo Dev | | `pagamento` | `automatico` ou `aguardar`. Veja [Pagar](pagar) | | `chave` | A chave usada na chamada: nome, prefixo, escopos e expiração | | `limites` | Limites diários da conexão. Os campos `*_por_minuto` valem por chave; `loja_por_minuto` soma todas as chaves do modo; `ip_por_minuto` vale por IP de origem | | `consumo_hoje` | Consumo desde a meia-noite de São Paulo | --- # Catálogo e domínios A API lê os produtos da sua marca cadastrados em **Meus produtos**. Ela não cria nem altera produtos. Aparecem os produtos ativos que têm modelo definido. Um produto sem modelo fica fora do catálogo até o cadastro ser completado. Todas as rotas exigem `catalogo:ler`. > **Dica:** sincronize uma vez por dia com `GET /catalogo/variantes` e guarde os SKUs no seu sistema. Não consulte o catálogo a cada pedido. ### GET /catalogo/produtos | Parâmetro | Descrição | |---|---| | `busca` | Parte do nome ou do código | | `situacao` | `ativo` (padrão) ou `todos` | | `limite`, `cursor` | [Paginação](convencoes#paginacao) | ```json { "objeto": "produto", "codigo": "CFT01", "nome": "Camiseta Folhas Tropicais", "modelo": { "codigo": "camiseta-algodao", "nome": "Camiseta Algodão" }, "situacao": "ativo", "imagem_url": "https://…/cft01.png", "cores": [ { "codigo": "PT", "nome": "Preto" }, { "codigo": "OW", "nome": "Off-white" } ], "tamanhos": ["P", "M", "G", "GG", "XGG"], "atualizado_em": "2026-10-10T18:22:01Z" } ``` - `codigo` é o SKU do produto definido em Meus produtos, ou um código interno da Gubo quando não houver. - `cores` traz só as **cores liberadas** do produto. ### GET /catalogo/produtos/{codigo} Devolve o produto com `variantes[]`. O `codigo` não diferencia maiúsculas. Produto inexistente: `404 produto_nao_encontrado`. ### GET /catalogo/variantes | Parâmetro | Descrição | |---|---| | `produto` | Código do produto | | `cor` | Código da cor | | `tamanho` | Código do tamanho | | `sku` | Até 50 SKUs separados por vírgula | | `disponivel` | `true` para só as disponíveis | | `limite`, `cursor` | Paginação | ```json { "objeto": "variante", "sku": "CFT01-PT-M", "sku_padrao": "CFT01-PT-M", "produto": "CFT01", "cor": { "codigo": "PT", "nome": "Preto" }, "tamanho": "M", "disponivel": true, "motivo_indisponivel": null, "preco_unitario_centavos": 5990 } ``` - `sku` é o SKU que você usa nos pedidos: o [vinculado](vinculos), se houver, ou o padrão. `sku_padrao` é sempre `--`. Os dois são aceitos na criação de pedidos. - No modo teste, `sku` mostra os vínculos feitos com chaves de teste. - O filtro `sku` encontra a variante pelos dois. `motivo_indisponivel`: `produto_inativo`, `cor_nao_liberada`, `tamanho_fora_de_linha` ou `variante_pausada`. O preço é de referência. O valor cobrado é o do pedido. ### GET /dominios/cores ```json { "objeto": "lista", "dados": [ { "objeto": "cor", "codigo": "PT", "nome": "Preto", "hex": "#1A1A1A" } ], "tem_mais": false, "proximo_cursor": null } ``` ### GET /dominios/tamanhos ```json { "objeto": "lista", "dados": [ { "objeto": "tamanho", "codigo": "P", "ordem": 2 } ], "tem_mais": false, "proximo_cursor": null } ``` Os domínios valem para toda a Gubo. As cores e os tamanhos válidos para cada produto estão no próprio produto. --- # Pedidos Como usar: [Criar pedidos](criar-pedido), [Pagar](pagar) e [Acompanhar](acompanhar). ## Objeto Pedido ```json { "objeto": "pedido", "id": "ped_01JA2X8Q7M3K5V9R2T4W6Y8Z0B", "modo": "producao", "referencia": "PED-10293", "numero": "V261010027", "situacao": "aguardando_pagamento", "versao": 3, "pendencias": [], "motivo_recusa": null, "itens": [ { "sku": "CFT01-PT-M", "produto": "CFT01", "nome": "Camiseta Folhas Tropicais", "cor": { "codigo": "PT", "nome": "Preto" }, "tamanho": "M", "quantidade": 2, "preco_unitario_centavos": 5990, "subtotal_centavos": 11980 } ], "valores": { "itens_centavos": 11980, "desconto_centavos": 599, "frete_centavos": 1776, "total_centavos": 13157 }, "frete": { "servico": "Olist Envios Jadlog", "servico_codigo": "jadlog", "transportadora": "Jadlog", "prazo_dias_uteis": 12, "previsao_entrega": "2026-10-27", "regra": "servico_definido", "prazo_em_risco": false, "simulado": false }, "entrega": { "prometida_ate": "2026-10-30", "servico": "jadlog", "se_indisponivel": "mais_barata" }, "pagamento": { "forma": "saldo", "situacao": "pendente", "pago_em": null, "motivo_recusa": null }, "envio": null, "destinatario": { "nome": "Ana Souza", "documento": "12345678909", "endereco": { "cep": "01310100", "uf": "SP" } }, "observacao": null, "metadados": { "id_erp": "88123" }, "criado_em": "2026-10-11T14:02:58Z", "atualizado_em": "2026-10-11T14:03:04Z" } ``` | Campo | Descrição | |---|---| | `situacao` | Veja o [ciclo de vida](acompanhar#ciclo-de-vida) | | `versao` | Cresce a cada mudança. Use para descartar webhooks antigos | | `pendencias[]` | `{ codigo, mensagem, desde }`. Vazio fora de `pendente` | | `motivo_recusa` | `{ codigo, mensagem }` quando `recusado` | | `itens[].sku` | O SKU da variante. Se a variante tem [vínculo](vinculos), é o SKU vinculado da sua loja | | `valores` | `itens_centavos` (soma bruta dos itens) − `desconto_centavos` (desconto do plano da marca) + `frete_centavos` = `total_centavos`. `null` até o frete ser escolhido | | `frete` | `null` até o frete ser escolhido. Veja os campos abaixo | | `pagamento.situacao` | `pendente`, `processando`, `pago` ou `recusado` | | `envio` | `null` até o envio. Depois: `rastreio`, `url_rastreio`, `transportadora`, `enviado_em`, `entregue_em` | **Campos de `frete`:** | Campo | Descrição | |---|---| | `servico`, `transportadora` | Nome do serviço e da transportadora | | `servico_codigo` | Código do serviço usado. Se for diferente de `entrega.servico`, a Gubo aplicou `se_indisponivel` | | `prazo_dias_uteis` | Produção (7 dias úteis) + transporte | | `previsao_entrega` | Data prevista, contada do pagamento. Antes do pagamento, contada a partir de hoje | | `regra` | `servico_definido`, `mais_barata`, `mais_rapida`, `mais_barata_no_prazo`, `prazo_em_risco` ou `manual` (escolhido pela equipe no Studio) | | `prazo_em_risco` | `true` quando a previsão passa de `entrega.prometida_ate` | | `simulado` | `true` no modo teste sem cotação real | Regras de escolha: [Fretes](fretes#como-o-frete-e-escolhido). ### POST /pedidos/por-variante Escopo `pedidos:criar`. Cabeçalhos: `Idempotency-Key` (obrigatório) e `Prefer: wait=N` (opcional, 1 a 15). | Campo | Obrigatório | Regras | |---|---|---| | `referencia` | sim | 1 a 64 caracteres. Única por modo | | `itens[].sku` | sim | SKU padrão da variante ou SKU [vinculado](vinculos) | | `itens[].quantidade` | sim | 1 a 100 | | `destinatario.nome` | sim | 3 a 120 caracteres | | `destinatario.documento` | sim | CPF ou CNPJ válido. Pontuação aceita | | `destinatario.email` | não | E-mail válido | | `destinatario.telefone` | não | 10 a 13 dígitos com DDD | | `destinatario.endereco.cep` | sim | 8 dígitos. Hífen aceito | | `destinatario.endereco.logradouro`, `numero`, `bairro`, `cidade` | sim | `numero` aceita `S/N` | | `destinatario.endereco.complemento` | não | Até 60 caracteres | | `destinatario.endereco.uf` | sim | Sigla de 2 letras | | `entrega.prometida_ate` | não | `AAAA-MM-DD`, de hoje até 120 dias | | `entrega.servico` | não | Código de [`GET /fretes/servicos`](fretes#get-fretes-servicos). Não combina com `preferencia` | | `entrega.preferencia` | não | `mais_barata` ou `mais_rapida` | | `entrega.se_indisponivel` | não | `pendencia` (padrão), `mais_barata` ou `mais_rapida`. Só com `servico` | | `observacao` | não | Até 500 caracteres. Aparece para a produção | | `metadados` | não | Até 20 chaves | | Status | Quando | |---|---| | `201` | Criado. Corpo: objeto Pedido | | `409` | `referencia_duplicada`, `idempotencia_conflito`, `idempotencia_em_andamento` | | `422` | `validacao` ou `itens_invalidos`, com `erro.detalhes[]`. `servico_desconhecido` ou `servico_inativo` | | `423` | `conexao_pausada` | | `429` | `limite_requisicoes`, `limite_pedidos_diario` | ```json { "erro": { "codigo": "itens_invalidos", "mensagem": "2 itens não puderam ser identificados no catálogo da sua marca.", "detalhes": [ { "campo": "itens[0].sku", "codigo": "sku_desconhecido", "mensagem": "O SKU CFT01-PT-XG não existe no catálogo da sua marca.", "sugestoes": ["CFT01-PT-GG", "CFT01-PT-G"] }, { "campo": "itens[2].sku", "codigo": "variante_indisponivel", "mensagem": "A variante CMO02-AR-P está indisponível (cor_nao_liberada)." } ], "documentacao": "https://gubo.com.br/docs-api/erros#itens_invalidos" }, "request_id": "req_01JA2Y0C4F…" } ``` ### POST /pedidos/por-produto Igual à rota anterior. Só muda o item: | Campo | Obrigatório | Regras | |---|---|---| | `itens[].produto` | sim | Código do produto | | `itens[].cor` | sim | Código (`PT`) ou nome (`Preto`) | | `itens[].tamanho` | sim | Código (`M`) | | `itens[].quantidade` | sim | 1 a 100 | O pedido criado traz o `sku` resolvido de cada item. ### GET /pedidos Escopo `pedidos:ler`. | Parâmetro | Descrição | |---|---| | `situacao` | Uma ou mais, separadas por vírgula | | `referencia` | Igualdade exata | | `criado_desde`, `criado_ate` | Data e hora | | `atualizado_desde` | Sincronização incremental. Ordena por `atualizado_em` crescente | | `limite`, `cursor` | Paginação | Sem `atualizado_desde`, a ordem é `criado_em` decrescente. ### GET /pedidos/{id} Escopo `pedidos:ler`. `404 pedido_nao_encontrado` se o id não existe neste modo e nesta conexão. ### POST /pedidos/{id}/pagar Escopo `pedidos:pagar`. Corpo: `{ "valor_esperado_centavos": 13157 }` (recomendado) ou `{}`. | Status | Quando | |---|---| | `202` | Aceito. `pagamento.situacao` é `processando`, ou `pago` com `Prefer: wait` | | `402` | `saldo_insuficiente` | | `409` | `pedido_nao_pagavel`, `pagamento_em_andamento`, `valor_divergente` | | `429` | `limite_valor_diario`, `limite_requisicoes` | ### POST /pedidos/{id}/cancelar Escopo `pedidos:cancelar`. Corpo: `{ "motivo": "até 200 caracteres" }` (opcional). `200` com o pedido `cancelado`. `409 pedido_nao_cancelavel` se a situação não permite. Cancelar de novo responde `200`. --- # Carteira O saldo que paga os pedidos. A recarga é feita no Studio, por Pix ou cartão. ### GET /carteira Escopo `carteira:ler`. ```json { "objeto": "carteira", "moeda": "BRL", "saldo_disponivel_centavos": 184250, "saldo_reservado_centavos": 14330, "atualizado_em": "2026-10-11T14:03:05Z" } ``` | Campo | Descrição | |---|---| | `saldo_disponivel_centavos` | O que pode ser usado agora | | `saldo_reservado_centavos` | Pagamentos em andamento | No modo teste, a carteira é fictícia e começa com R$ 10.000,00. Para ser avisado de saldo baixo, use o evento [`carteira.saldo_baixo`](webhooks#eventos). --- # Rotas de teste Só funcionam com chave `gubo_test_`. Com chave de produção, a resposta é `403 modo_incompativel`. Como usar: [Modo teste](modo-teste). ### POST /teste/pedidos/{id}/avancar Corpo: `{ "para": "em_producao" | "enviado" | "entregue" }`. Avança o pedido passando pelas situações intermediárias e dispara um webhook para cada uma. O pedido precisa estar `confirmado` ou adiante. Se não estiver, ou se o destino ficar para trás, a resposta é `409 avanco_invalido`, com `detalhes.situacao`. ### POST /teste/carteira Corpo: `{ "saldo_centavos": 0 }`. Define o saldo da carteira de teste, de 0 a 100.000.000. --- # Exemplos Clientes completos, sem dependências além do básico de cada linguagem. Eles já fazem autenticação, idempotência, repetição segura e tratamento de erro. Copie e adapte. ## Fluxo inteiro em cURL ```bash cURL export GUBO_API_KEY=gubo_test_... API=https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1 # 1. conferir a chave curl -s $API/conexao -H "Authorization: Bearer $GUBO_API_KEY" # 2. cotar o frete e ver a escolha (opcional) curl -s $API/fretes/cotacoes \ -H "Authorization: Bearer $GUBO_API_KEY" -H "Content-Type: application/json" \ -d '{ "cep": "30140-071", "itens": [ { "produto": "CFT01", "cor": "PT", "tamanho": "G", "quantidade": 1 } ], "entrega": { "preferencia": "mais_barata" } }' # 3. criar pedido por produto, cor e tamanho, com a mesma entrega da cotação curl -s $API/pedidos/por-produto \ -H "Authorization: Bearer $GUBO_API_KEY" -H "Content-Type: application/json" \ -H "Idempotency-Key: 3f1c9a52-6a0e-4c1e-9d7b-2b1f0d6c8e11" -H "Prefer: wait=10" \ -d '{ "referencia": "PED-10294", "itens": [ { "produto": "CFT01", "cor": "PT", "tamanho": "G", "quantidade": 1 } ], "destinatario": { "nome": "Bruno Lima", "documento": "123.456.789-09", "endereco": { "cep": "30140-071", "logradouro": "Rua da Bahia", "numero": "1148", "bairro": "Centro", "cidade": "Belo Horizonte", "uf": "MG" } }, "entrega": { "preferencia": "mais_barata" } }' # 4. pagar (conexão em "aguardar") curl -s $API/pedidos/ped_.../pagar \ -H "Authorization: Bearer $GUBO_API_KEY" -H "Content-Type: application/json" \ -H "Idempotency-Key: pagar-ped_..." -d '{ "valor_esperado_centavos": 7980 }' # 5. sincronizar mudanças curl -s "$API/pedidos?atualizado_desde=2026-10-11T00:00:00Z&limite=100" -H "Authorization: Bearer $GUBO_API_KEY" ``` ## Cliente O mesmo cliente em três linguagens. Todos repetem só o que é seguro repetir, respeitam `Retry-After`, reaproveitam a `Idempotency-Key` e tratam `409 referencia_duplicada` como sucesso. ```js Node.js // gubo.js import crypto from "node:crypto"; const BASE = "https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1"; const REPETIVEIS = new Set(["limite_requisicoes", "idempotencia_em_andamento", "erro_interno", "indisponivel_temporariamente"]); export class GuboErro extends Error { constructor(status, corpo) { super(corpo?.erro?.mensagem ?? `HTTP ${status}`); this.status = status; this.codigo = corpo?.erro?.codigo; this.detalhes = corpo?.erro?.detalhes; this.requestId = corpo?.request_id; } } export class Gubo { constructor(chave = process.env.GUBO_API_KEY, { tentativas = 5, tempoLimiteMs = 30000 } = {}) { if (!chave) throw new Error("Defina GUBO_API_KEY"); this.chave = chave; this.tentativas = tentativas; this.tempoLimiteMs = tempoLimiteMs; } async #chamar(metodo, rota, { corpo, idempotencia, espera } = {}) { const cab = { Authorization: `Bearer ${this.chave}`, Accept: "application/json" }; if (corpo !== undefined) cab["Content-Type"] = "application/json"; if (metodo === "POST") cab["Idempotency-Key"] = idempotencia ?? crypto.randomUUID(); if (espera) cab["Prefer"] = `wait=${espera}`; for (let t = 0; ; t++) { let res; try { res = await fetch(BASE + rota, { method: metodo, headers: cab, body: corpo === undefined ? undefined : JSON.stringify(corpo), signal: AbortSignal.timeout(this.tempoLimiteMs), }); } catch (falhaDeRede) { if (t + 1 >= this.tentativas) throw falhaDeRede; await dormir(recuo(t)); continue; // mesma Idempotency-Key } const json = await res.json().catch(() => null); if (res.ok) return json; const erro = new GuboErro(res.status, json); const repetivel = res.status >= 500 || REPETIVEIS.has(erro.codigo); if (!repetivel || t + 1 >= this.tentativas) throw erro; const ra = Number(res.headers.get("Retry-After")); await dormir(ra > 0 ? ra * 1000 : recuo(t)); } } conexao() { return this.#chamar("GET", "/conexao"); } carteira() { return this.#chamar("GET", "/carteira"); } pedido(id) { return this.#chamar("GET", `/pedidos/${id}`); } listarPedidos(filtros = {}) { return this.#chamar("GET", `/pedidos?${new URLSearchParams(filtros)}`); } async *todosOsPedidos(filtros = {}) { let cursor; do { const pagina = await this.listarPedidos({ ...filtros, limite: 100, ...(cursor && { cursor }) }); yield* pagina.dados; cursor = pagina.tem_mais ? pagina.proximo_cursor : null; } while (cursor); } /** idempotencia: gere e GRAVE antes de chamar; reuse a mesma em toda repetição */ async criarPorVariante(pedido, idempotencia) { try { return await this.#chamar("POST", "/pedidos/por-variante", { corpo: pedido, idempotencia, espera: 10 }); } catch (e) { if (e.codigo === "referencia_duplicada") return this.pedido(e.detalhes.pedido_id); throw e; } } criarPorProduto(pedido, idempotencia) { return this.#chamar("POST", "/pedidos/por-produto", { corpo: pedido, idempotencia, espera: 10 }); } pagar(id, valorEsperadoCentavos) { return this.#chamar("POST", `/pedidos/${id}/pagar`, { corpo: valorEsperadoCentavos ? { valor_esperado_centavos: valorEsperadoCentavos } : {}, idempotencia: `pagar-${id}`, espera: 10, }); } cancelar(id, motivo) { return this.#chamar("POST", `/pedidos/${id}/cancelar`, { corpo: motivo ? { motivo } : {}, idempotencia: `cancelar-${id}` }); } servicosDeFrete() { return this.#chamar("GET", "/fretes/servicos"); } /** devolve a cotação pronta; espera enquanto estiver pendente */ async cotarFrete({ cep, itens, entrega }) { let c = await this.#chamar("POST", "/fretes/cotacoes", { corpo: { cep, itens, ...(entrega && { entrega }) } }); while (c.situacao === "pendente") c = await this.#chamar("GET", `/fretes/cotacoes/${c.id}`, { espera: 5 }); return c; } async *todosOsVinculos(filtros = {}) { let cursor; do { const pagina = await this.#chamar("GET", `/vinculos?${new URLSearchParams({ ...filtros, limite: 100, ...(cursor && { cursor }) })}`); yield* pagina.dados; cursor = pagina.tem_mais ? pagina.proximo_cursor : null; } while (cursor); } /** itens: até 100 { variante, sku }; sku null remove o vínculo */ gravarVinculos(itens, simular = false) { return this.#chamar("PUT", "/vinculos", { corpo: { itens, simular } }); } } const dormir = (ms) => new Promise((r) => setTimeout(r, ms)); const recuo = (t) => Math.min(2 ** t * 1000, 300000) * (0.5 + Math.random() / 2); ``` ```python Python # gubo.py import os, random, time, uuid import requests BASE = "https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1" REPETIVEIS = {"limite_requisicoes", "idempotencia_em_andamento", "erro_interno", "indisponivel_temporariamente"} class GuboErro(Exception): def __init__(self, status, corpo): erro = (corpo or {}).get("erro", {}) super().__init__(erro.get("mensagem", f"HTTP {status}")) self.status, self.codigo = status, erro.get("codigo") self.detalhes, self.request_id = erro.get("detalhes"), (corpo or {}).get("request_id") class Gubo: def __init__(self, chave=None, tentativas=5, tempo_limite=30): self.chave = chave or os.environ["GUBO_API_KEY"] self.tentativas, self.tempo_limite = tentativas, tempo_limite self.s = requests.Session() self.s.headers.update({"Authorization": f"Bearer {self.chave}", "Accept": "application/json"}) def _chamar(self, metodo, rota, corpo=None, idempotencia=None, espera=None, params=None): cab = {} if metodo == "POST": cab["Idempotency-Key"] = idempotencia or str(uuid.uuid4()) if espera: cab["Prefer"] = f"wait={espera}" for t in range(self.tentativas): try: r = self.s.request(metodo, BASE + rota, json=corpo, params=params, headers=cab, timeout=self.tempo_limite) except requests.RequestException: if t + 1 >= self.tentativas: raise time.sleep(_recuo(t)); continue # mesma Idempotency-Key if r.ok: return r.json() try: j = r.json() except ValueError: j = None e = GuboErro(r.status_code, j) if not (r.status_code >= 500 or e.codigo in REPETIVEIS) or t + 1 >= self.tentativas: raise e ra = r.headers.get("Retry-After") time.sleep(int(ra) if ra and ra.isdigit() else _recuo(t)) def conexao(self): return self._chamar("GET", "/conexao") def carteira(self): return self._chamar("GET", "/carteira") def pedido(self, id): return self._chamar("GET", f"/pedidos/{id}") def todos_os_pedidos(self, **filtros): cursor = None while True: p = self._chamar("GET", "/pedidos", params={**filtros, "limite": 100, **({"cursor": cursor} if cursor else {})}) yield from p["dados"] if not p["tem_mais"]: return cursor = p["proximo_cursor"] def criar_por_variante(self, pedido, idempotencia): try: return self._chamar("POST", "/pedidos/por-variante", pedido, idempotencia, espera=10) except GuboErro as e: if e.codigo == "referencia_duplicada": return self.pedido(e.detalhes["pedido_id"]) raise def criar_por_produto(self, pedido, idempotencia): return self._chamar("POST", "/pedidos/por-produto", pedido, idempotencia, espera=10) def pagar(self, id, valor_esperado_centavos=None): corpo = {"valor_esperado_centavos": valor_esperado_centavos} if valor_esperado_centavos else {} return self._chamar("POST", f"/pedidos/{id}/pagar", corpo, f"pagar-{id}", espera=10) def cancelar(self, id, motivo=None): return self._chamar("POST", f"/pedidos/{id}/cancelar", {"motivo": motivo} if motivo else {}, f"cancelar-{id}") def servicos_de_frete(self): return self._chamar("GET", "/fretes/servicos") def cotar_frete(self, cep, itens, entrega=None): c = self._chamar("POST", "/fretes/cotacoes", {"cep": cep, "itens": itens, **({"entrega": entrega} if entrega else {})}) while c["situacao"] == "pendente": c = self._chamar("GET", f"/fretes/cotacoes/{c['id']}", espera=5) return c def todos_os_vinculos(self, **filtros): cursor = None while True: p = self._chamar("GET", "/vinculos", params={**filtros, "limite": 100, **({"cursor": cursor} if cursor else {})}) yield from p["dados"] if not p["tem_mais"]: return cursor = p["proximo_cursor"] def gravar_vinculos(self, itens, simular=False): """itens: até 100 {"variante", "sku"}; sku None remove o vínculo""" return self._chamar("PUT", "/vinculos", {"itens": itens, "simular": simular}) def _recuo(t): return min(2 ** t, 300) * (0.5 + random.random() / 2) ``` ```php PHP chave ??= getenv('GUBO_API_KEY') ?: throw new InvalidArgumentException('Defina GUBO_API_KEY'); } private function chamar(string $metodo, string $rota, ?array $corpo = null, ?string $idem = null, ?int $espera = null): array { $cab = ['Authorization: Bearer ' . $this->chave, 'Accept: application/json']; if ($corpo !== null) $cab[] = 'Content-Type: application/json'; if ($metodo === 'POST') $cab[] = 'Idempotency-Key: ' . ($idem ?? bin2hex(random_bytes(16))); if ($espera) $cab[] = "Prefer: wait=$espera"; for ($t = 0; ; $t++) { $retryAfter = 0; $ch = curl_init(self::BASE . $rota); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => $metodo, CURLOPT_HTTPHEADER => $cab, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_POSTFIELDS => match (true) { $corpo === null => null, $corpo === [] => '{}', // corpo vazio precisa ser objeto default => json_encode($corpo, JSON_UNESCAPED_UNICODE), }, CURLOPT_HEADERFUNCTION => function ($c, $linha) use (&$retryAfter) { if (stripos($linha, 'Retry-After:') === 0) $retryAfter = (int) trim(substr($linha, 12)); return strlen($linha); }, ]); $resp = curl_exec($ch); $status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE); curl_close($ch); if ($resp === false) { // falha de rede: mesma chave if ($t + 1 >= $this->tentativas) throw new RuntimeException('Falha de rede'); usleep(self::recuo($t)); continue; } $json = json_decode($resp, true) ?? []; if ($status >= 200 && $status < 300) return $json; $e = new GuboErro($status, $json['erro']['codigo'] ?? null, $json['erro']['detalhes'] ?? null, $json['request_id'] ?? null, $json['erro']['mensagem'] ?? "HTTP $status"); $repetivel = $status >= 500 || in_array($e->codigo, self::REPETIVEIS, true); if (!$repetivel || $t + 1 >= $this->tentativas) throw $e; usleep($retryAfter > 0 ? $retryAfter * 1_000_000 : self::recuo($t)); } } private static function recuo(int $t): int { return (int) (min(2 ** $t, 300) * 1_000_000 * (0.5 + mt_rand() / mt_getrandmax() / 2)); } public function conexao(): array { return $this->chamar('GET', '/conexao'); } public function carteira(): array { return $this->chamar('GET', '/carteira'); } public function pedido(string $id): array { return $this->chamar('GET', "/pedidos/$id"); } public function criarPorVariante(array $pedido, string $idempotencia): array { try { return $this->chamar('POST', '/pedidos/por-variante', $pedido, $idempotencia, 10); } catch (GuboErro $e) { if ($e->codigo === 'referencia_duplicada') return $this->pedido($e->detalhes['pedido_id']); throw $e; } } public function pagar(string $id, ?int $valorEsperado = null): array { return $this->chamar('POST', "/pedidos/$id/pagar", $valorEsperado ? ['valor_esperado_centavos' => $valorEsperado] : [], "pagar-$id", 10); } public function cancelar(string $id, ?string $motivo = null): array { return $this->chamar('POST', "/pedidos/$id/cancelar", $motivo ? ['motivo' => $motivo] : [], "cancelar-$id"); } public function cotarFrete(string $cep, array $itens, ?array $entrega = null): array { $c = $this->chamar('POST', '/fretes/cotacoes', array_filter(['cep' => $cep, 'itens' => $itens, 'entrega' => $entrega])); while ($c['situacao'] === 'pendente') $c = $this->chamar('GET', "/fretes/cotacoes/{$c['id']}", null, null, 5); return $c; } /** $itens: até 100 ['variante' => ..., 'sku' => ...]; sku null remove o vínculo */ public function gravarVinculos(array $itens, bool $simular = false): array { return $this->chamar('PUT', '/vinculos', ['itens' => $itens, 'simular' => $simular]); } } ``` > **Importante:** em PHP, `json_encode([])` gera `[]`. A API espera um objeto (`{}`) no corpo de `/pagar` e `/cancelar`, por isso o cliente envia `{}` quando o corpo está vazio. ## Usando o cliente ```js Node.js // uso import { Gubo, GuboErro } from "./gubo.js"; const gubo = new Gubo(); const chave = await meuBanco.idempotenciaDoPedido("PED-10293"); // gerada e gravada antes try { const p = await gubo.criarPorVariante({ referencia: "PED-10293", itens: [{ sku: "CFT01-PT-M", quantidade: 2 }], destinatario: { nome: "Ana Souza", documento: "12345678909", endereco: { cep: "01310100", logradouro: "Av. Paulista", numero: "1000", bairro: "Bela Vista", cidade: "São Paulo", uf: "SP" } }, }, chave); if (p.situacao === "aguardando_pagamento") await gubo.pagar(p.id, p.valores.total_centavos); } catch (e) { if (e instanceof GuboErro && e.codigo === "itens_invalidos") { for (const d of e.detalhes) console.warn(d.campo, d.codigo, d.sugestoes ?? ""); } else if (e instanceof GuboErro && e.codigo === "saldo_insuficiente") { avisarFinanceiro(e.detalhes.falta_centavos); } else throw e; } ``` ## Vincular os SKUs da loja ```js Node.js // liga as variantes sem vínculo aos SKUs da loja, 100 por vez, simulando antes const pares = []; for await (const v of gubo.todosOsVinculos({ situacao: "sem_vinculo" })) { const sku = minhaLoja.skuPara(v.produto.codigo, v.cor.codigo, v.tamanho); // a sua regra de correspondência if (sku) pares.push({ variante: v.variante, sku }); } for (let i = 0; i < pares.length; i += 100) { const lote = pares.slice(i, i + 100); const teste = await gubo.gravarVinculos(lote, true); const ok = lote.filter((_, j) => teste.resultados[j].resultado === "valido"); teste.resultados.filter((r) => r.resultado === "rejeitado").forEach((r) => paraRevisar(r.variante, r.erro)); if (ok.length) await gubo.gravarVinculos(ok); } ``` ## Postman e Insomnia Importe o [`openapi.yaml`](https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/openapi.yaml). Crie a variável `GUBO_API_KEY` no ambiente e use autenticação **Bearer** com `{{GUBO_API_KEY}}`. Nos `POST`, gere o `Idempotency-Key` com `{{$guid}}` (Postman) ou `{% uuid 'v4' %}` (Insomnia). --- # Integrar com IA Esta documentação também foi escrita para assistentes de código (Claude, ChatGPT, Cursor, Copilot) e para agentes. ## Arquivos para máquinas | Arquivo | Uso | |---|---| | [`https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/llms.txt`](https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/llms.txt) | Índice curto, no padrão llms.txt. O assistente descobre o que existe | | [`https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/llms-full.txt`](https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/llms-full.txt) | Toda a documentação num arquivo só. Cole no contexto do assistente | | [`https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/openapi.yaml`](https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/openapi.yaml) | Contrato OpenAPI 3.1. Gera cliente, valida chamadas e cria ferramentas para agentes | | `https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/docs/.md` | Cada página desta documentação em Markdown puro | Em toda página há o botão **Copiar como Markdown**. ## Prompt recomendado Cole no assistente, junto com o `llms-full.txt`: ```text Você vai integrar com a API Gubo v1 (https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1) para criar pedidos de produção e acompanhá-los até a entrega. Use somente o que está na documentação anexada. Não invente rotas, campos ou códigos. Requisitos: 1. A chave vem da variável de ambiente GUBO_API_KEY. Nunca escreva a chave no código. 2. Criar, pagar e cancelar enviam Idempotency-Key. Ao criar pedido, gere um UUID, grave no banco ANTES da chamada e reuse a mesma chave em toda repetição. 3. referencia = número do pedido no meu sistema. 4. Repita só em 429, 5xx, falha de rede e 409 idempotencia_em_andamento, respeitando Retry-After. Nunca repita outros 4xx sem corrigir. 5. 409 referencia_duplicada é sucesso: use erro.detalhes.pedido_id. 6. Dinheiro em centavos inteiros (*_centavos). Nunca use float. 7. Situação desconhecida do pedido = "em andamento". 8. Webhook: HMAC-SHA256 sobre ".", comparação em tempo constante, tolerância de 5 min; deduplique por evento.id; ignore se pedido.versao <= versão local; responda 2xx rápido. 9. Sincronize com GET /pedidos?atualizado_desde= a cada 5 minutos. 10. Registre o request_id de toda resposta e ignore campos desconhecidos. Comece pelos testes, com uma chave gubo_test_ e os CEPs de cenário do modo teste. ``` ## Erros comuns em código gerado | Errado | Certo | |---|---| | `Idempotency-Key` nova a cada tentativa | A mesma chave em todas as repetições da mesma operação | | `preco: 59.9` | `preco_unitario_centavos: 5990` | | Inventar `POST /pedidos` | São duas rotas: `/pedidos/por-variante` e `/pedidos/por-produto` | | `entrega.servico` junto com `entrega.preferencia` | Use um dos dois. A alternativa ao serviço vai em `se_indisponivel` | | Inventar código de transportadora | Use os códigos de `GET /fretes/servicos` | | Enviar campos extras "por garantia" | Campo desconhecido gera `422 campo_desconhecido` | | Repetir em `422` ou `401` | Corrija. Repetir só leva a bloqueio de IP | | Assinar `JSON.stringify(req.body)` | Use o corpo bruto | | Chave na query string | Só `Authorization: Bearer` | | Consultar o pedido em laço logo após criar | Use `Prefer: wait=10` ou webhooks | ## Agentes que criam pedidos - Dê ao agente uma **chave própria**, com o menor escopo possível. Quem só cria pedidos não precisa de `pedidos:pagar`. - Deixe a conexão em `aguardar` e pague por outro caminho, com revisão humana. - Baixe os **limites diários** para o volume esperado. Eles são o freio de segurança. - Peça ao agente que mostre `valores.total_centavos` e envie `valor_esperado_centavos` ao pagar. - Exponha ao agente só as rotas de que ele precisa, geradas do `openapi.yaml`. ## Vincular produtos Um agente pode ligar o catálogo da sua loja virtual às variantes da Gubo. Crie uma chave com o atalho **Vínculos**: ela não cria nem paga pedidos. Comece com a chave de teste. ```text Você vai ligar os produtos da minha loja às variantes da Gubo pela API Gubo v1 (https://kx75xapy8azwdvdnd4brts7y098e5qvg.buildprintusercontent.com/gubo-api/latest/live/v1), usando só o que está na documentação anexada (página Vínculos de SKU). Roteiro: 1. Leia GET /vinculos?situacao=sem_vinculo&limite=100 e siga proximo_cursor até tem_mais = false. 2. Para cada variante da Gubo, encontre na minha loja o produto do mesmo modelo, cor e tamanho. Compare pelo nome do produto, cor (código e nome) e tamanho. Não use só semelhança de texto. 3. Se houver dúvida ou mais de um candidato, NÃO vincule. Coloque numa lista "para revisar". 4. Monte lotes de até 100 pares { variante, sku } e envie PUT /vinculos com "simular": true. 5. Corrija os itens com resultado "rejeitado" pelo erro.codigo. Nunca troque o SKU de outra variante para resolver sku_em_uso: coloque o caso na lista "para revisar". 6. Envie os mesmos lotes sem "simular". Respeite RateLimit-Remaining e Retry-After. 7. Consulte GET /vinculos?situacao=pendente até esvaziar e depois GET /vinculos?situacao=falhou. 8. No fim, mostre: quantos vinculados, a lista "para revisar" e os que falharam. ``` - Revise uma amostra dos vínculos antes de rodar com a chave de produção. Vínculo errado faz o pedido sair com a peça errada. - O agente pode refazer o roteiro quantas vezes quiser. Lotes repetidos voltam como `inalterado` e não contam no limite. ## Agentes que cotam o frete - Use `POST /fretes/cotacoes` para mostrar opções ao seu cliente e envie no pedido o mesmo bloco `entrega` da cotação. - Peça ao agente que confira `frete.servico_codigo` e `valores.total_centavos` do pedido antes de pagar. Um servidor MCP oficial está previsto para a fase 2, com os mesmos escopos e limites da chave. --- # Changelog ## Política - Dentro da `/v1`, a Gubo só **acrescenta**: campos, campos opcionais de envio, rotas, eventos, valores de enum e cabeçalhos. Ignore o que você não conhece. - Mudança que quebra (remover ou renomear campo, mudar tipo ou significado, tornar obrigatório) só numa versão nova, com pelo menos **12 meses** de convivência e aviso por e-mail e no Gubo Dev. - Rotas em descontinuação respondem com os cabeçalhos `Deprecation` e `Sunset`. - Mudanças nos limites padrão são avisadas com 30 dias de antecedência e nunca reduzem limites já configurados. ## v1.1.1 · Limites em três barreiras - Os limites por minuto passam a valer em três barreiras: IP de origem, chave e loja (todas as chaves do modo somadas, o dobro do limite por chave). - `429 limite_requisicoes` traz `detalhes.barreira` (`ip`, `chave` ou `loja`) e `detalhes.limite_por_minuto`. - `GET /conexao` ganhou `limites.loja_por_minuto` e `limites.ip_por_minuto`. - Instabilidade momentânea responde `503 indisponivel_temporariamente` com `Retry-After: 1`. Leituras repetem sozinhas antes de responder. - `GET /vinculos?busca=` também encontra o SKU da loja vinculado no modo teste. ## v1.1.0 · Fretes e vínculos de SKU - `GET /fretes/servicos`, `POST /fretes/cotacoes` e `GET /fretes/cotacoes/{id}`: cotação com os preços finais da Gubo, opções, recomendações e a escolha que o pedido faria. - `entrega.servico` escolhe a transportadora exata. `entrega.se_indisponivel` decide o que fazer se ela não atender o CEP. - `entrega.preferencia` passa a aceitar `mais_barata` e `mais_rapida`. - Nova pendência `servico_indisponivel`. - `frete` ganhou `servico_codigo`, `regra` e `prazo_em_risco`. `valores` ganhou `desconto_centavos`. - `GET /vinculos` e `PUT /vinculos`: ligam o SKU da sua loja à variante da Gubo, até 100 por chamada, com simulação. - O SKU vinculado é aceito na criação de pedidos. `GET /catalogo/variantes` ganhou `sku_padrao`. - Escopos `fretes:cotar`, `vinculos:ler` e `vinculos:escrever`, e o atalho **Vínculos**. - Limites: 30 cotações por minuto, 20 gravações de vínculos por minuto, 1.000 cotações e 5.000 vínculos por dia. - `GET /conexao` mostra os novos limites e o consumo de cotações e vínculos. ## v1.0.0 · Lançamento em piloto - Chaves com modos teste e produção, escopos, IPs permitidos, expiração e rotação. - Catálogo: produtos, variantes, cores e tamanhos. - Pedidos: criação por variante e por produto, consulta, lista com sincronização incremental, pagamento com saldo e cancelamento antes do pagamento. - Carteira: saldo. - Idempotência obrigatória, `referencia` única e `Prefer: wait`. - Webhooks assinados, com novas tentativas por 3 dias. - Modo teste com frete simulado, carteira fictícia, avanço manual e CEPs de cenário. - `llms.txt`, `llms-full.txt` e `openapi.yaml`. ## Próximas, sem data - Extrato da carteira e recarga por Pix pela API. - Cancelamento de pedido pago, com estorno para a carteira. - Papel "Desenvolvedor" no Gubo Dev. - SDKs oficiais para Node.js e Python e servidor MCP.