Pular para o conteúdo

Toki · API

Cobre pelo seu sistema. Que paguem com Toki.

Seu caixa, sua loja online ou seu ERP criam uma cobrança com uma chamada. A Toki devolve um link de pagamento; seu cliente abre, paga pela carteira dele e volta para o seu site. O dinheiro chega à sua conta Toki na hora.

URL basehttps://api.toki.lat/v1
Criar uma credencial

Começar

Como funciona

  • 1. Seu sistema chama POST /v1/cobros com o valor.
  • 2. A Toki responde com uma cobrança e uma url_checkout.
  • 3. Você manda seu cliente para lá, ou incorpora na sua própria página.
  • 4. Ele paga com o app da Toki e o devolvemos para a sua url_retorno.
  • 5. Avisamos por webhook (ou você consulta o status da cobrança).

Sua credencial pede dinheiro; nunca o cobra. Nenhuma chamada a esta API movimenta dinheiro: a cobrança é confirmada pela pessoa no celular dela, da própria sessão e da própria carteira. Se sua chave vazasse, quem a tivesse poderia gerar cobranças em seu nome — chato, e revogável em um clique — mas não tiraria um centavo de ninguém.

Autenticação

Cada chamada leva sua credencial no header Authorization. O token tem a forma <key_id>.<segredo>; você o recebe inteiro ao criar a credencial no seu painel, e só nessa vez.

Shell
Authorization: Bearer tk_9f2c8ab1de4057.9d31...c0

No painel você também pode restringir de quais IPs a credencial é aceita. Se configurar, uma chamada de outro endereço é recusada mesmo com o segredo certo. É a diferença entre uma chave vazada que funciona de qualquer lugar e uma que não serve fora do seu servidor.

Limite: 120 chamadas por minuto por credencial, e 600 por minuto a partir de um mesmo IP (contadas antes de verificar a chave, para frear quem tenta adivinhar segredos). Passar disso responde 429 com reintentar_en_s.

Escopos

Cada credencial carrega uma lista de escopos, e cada rota exige o seu. Servem para você entregar uma chave a um integrador externo sem entregar o seu caixa: uma chave feita para montar pedidos não deveria conseguir devolver o dinheiro de uma cobrança.

EscopoO que libera
cobrosPedir dinheiro, e desfazer: POST /v1/cobros, GET /v1/cobros/{id}, /anular, /reembolsar, /simular-pago, todo /v1/servicios e POST /v1/suscripciones.
pedidosO checkout de micro apps: POST /v1/pedidos, GET /v1/pedidos/{id}, /consumir e /v1/apps/usos.
equipoO chat da sua marca: GET /v1/canales, POST /v1/canales/{id}/mensajes e /v1/invocaciones/{id} (ler e responder). Não toca em dinheiro.

Duas rotas ficam fora da tabela de propósito: GET /v1/comercio não exige escopo —é «quem sou eu», e não diz nada que a credencial já não represente— e GET /v1/cobros/{id}/qr não pede credencial, porque vai impressa num recibo.

Três respostas que não se confundem. 403 sin_alcance é «sua chave não tem essa permissão» — a mensagem diz qual falta e quais você tem, então se resolve emitindo outra credencial, não trocando o id. 404 no_encontrado é «esse id não existe, ou não é desta credencial»: as duas respondem igual de propósito, porque distingui-las diria a qualquer um quais ids existem na Toki. 409 estado_invalido é «existe e é seu, mas já não está pendente».

Moeda

A moeda de tudo o que você cobra é a da sua carteira Toki. Não se escolhe por cobrança: um comércio cobra em uma moeda, a dele.

Os valores vão sempre na menor unidade dessa moeda, o padrão de qualquer gateway. O peso chileno não se subdivide, então 15990 são quinze mil novecentos e noventa pesos. Numa moeda com centavos, 1599 são 15,99.

GET/v1/comercio

Diz em que moeda você cobra, o nome do seu comércio e se a credencial é de teste ou de produção. Use para validar sua configuração sem criar uma cobrança de mentira.

JSON
{ "comercio": { "nombre": "Minha Loja", "moneda": "CLP", "modo": "prueba" } }

Se sua loja cobra em moeda diferente da sua carteira, não integre ainda. A Toki não converte: cobraria o número enviado como se fosse na sua moeda. Fale conosco antes.

Cobrar

Criar uma cobrança

POST/v1/cobros
CampoTipoO que é
montointeiro, obrigatórioNa menor unidade da sua moeda. Em CLP o peso não se divide: 15990 são quinze mil novecentos e noventa pesos. Numa moeda com centavos, 1599 são 15,99.
conceptotextoO que seu cliente vê. "Nota 4471", "Mesa 12".
referencia_externatextoSeu identificador. Também torna a cobrança idempotente: repetir com a mesma referência devolve a cobrança que já existe, em vez de criar outra.
url_retornohttpsPara onde seu cliente volta depois de pagar. Com uma credencial de teste também se aceita http (aqui e em url_cancelacion), para integrar a partir de localhost.
url_cancelacionhttpsPara onde volta se desistir.
expira_minutosinteiroEntre 1 e 1440. Padrão 15.
metadataobjetoO que você quiser guardar. Devolvemos igual no webhook, exceto as chaves reservadas origen, items, dte e dte_error, que são descartadas.
curl -X POST https://api.toki.lat/v1/cobros \
  -H "Authorization: Bearer $TOKI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "monto": 15990,
    "concepto": "Boleta 4471",
    "referencia_externa": "b-4471",
    "url_retorno": "https://mitienda.cl/gracias",
    "url_cancelacion": "https://mitienda.cl/carro",
    "metadata": { "caja": "3" }
  }'
JSON
{
  "cobro": {
    "id": "9cd827ca-d10d-4768-8627-265d875f1cd2",
    "monto": 15990,
    "moneda": "CLP",
    "concepto": "Nota 4471",
    "estado": "pending",
    "reembolsado": 0,
    "es_prueba": false,
    "referencia_externa": "b-4471",
    "metadata": { "caixa": "3" },
    "expira_en": "2026-08-25T15:44:57Z",
    "creado_en": "2026-08-25T15:29:57Z",
    "pagado_en": null,
    "url_checkout": "https://toki.lat/pagar/9cd827ca...",
    "qr_svg": "https://toki.lat/api/v1/cobros/9cd827ca.../qr",
    "deeplink": "toki://pay/9cd827ca..."
  }
}

Use `url_checkout`: é a página de pagamento que hospedamos. qr_svg e deeplink ficam para quem quiser montar a própria tela — leia a seção seguinte antes de decidir isso.

A página de pagamento

Mandar seu cliente para a url_checkout é a forma recomendada de cobrar, e não só por comodidade.

Um QR não pode ser escaneado pelo mesmo celular que o mostra. Se seu cliente está comprando na sua loja pelo celular — o caso mais comum — uma imagem de QR não serve para nada. Nossa página detecta isso: no celular oferece abrir o app direto, e no desktop mostra o QR.

Ela também acompanha o status sozinha, mostra quanto falta para a cobrança vencer, e devolve o cliente ao seu site quando termina. E como a página é nossa, podemos melhorar o fluxo ou acrescentar meios de pagamento sem que você publique nada de novo.

Incorporar no seu site

Se preferir que seu cliente não saia da sua página, carregue-a em um iframe. Avisamos o resultado com postMessage, então você não precisa consultar nada pelo navegador.

HTML
<iframe
  src="https://toki.lat/pagar/9cd827ca..."
  style="width:100%;max-width:420px;height:620px;border:0"
  allow="clipboard-write"></iframe>

<script>
  window.addEventListener('message', (e) => {
    if (e.origin !== 'https://toki.lat') return;      // verifique SEMPRE a origem
    if (e.data?.fuente !== 'toki') return;
    if (e.data.evento === 'cobro.paid') {
      // Confirme contra o SEU servidor antes de entregar: uma mensagem do
      // navegador qualquer um pode forjar. Isto serve para reagir na tela.
      mostrarObrigado();
    }
  });
</script>

O `postMessage` é para a interface, não para decidir. Antes de entregar um produto, confirme com GET /v1/cobros/{id} do seu servidor ou espere o webhook. Qualquer um pode mandar uma mensagem para sua página; ninguém pode forjar nossa resposta assinada.

O QR

GET/v1/cobros/{id}/qr

Devolve o código em SVG, para ficar nítido tanto em uma bobina térmica quanto em uma tela de caixa. É a única rota que não pede credencial: ela vai impressa em notas, onde não há onde colocar um header. O que expõe é o mesmo id que já vai dentro do código, e com esse id só dá para pagar.

Use quando o pagamento acontece na sua frente — um caixa, uma nota impressa — onde seu cliente tem o próprio celular para escanear. Para cobrar pela internet, use a página de pagamento.

HTML
<img src="https://api.toki.lat/v1/cobros/{id}/qr" alt="Pague com Toki" />

Consultar uma cobrança

GET/v1/cobros/{id}

Devolve a cobrança com seu estado: pending, paid, cancelled ou expired. É o caminho reserva se você não puder receber webhooks — um caixa atrás de uma rede fechada integra só com isto.

curl https://api.toki.lat/v1/cobros/9cd827ca-d10d-4768-8627-265d875f1cd2 \
  -H "Authorization: Bearer $TOKI_API_KEY"

Cancelar uma cobrança

POST/v1/cobros/{id}/anular

Só enquanto estiver pending. Uma cobrança já paga não se cancela por aqui: devolver dinheiro é um reembolso, com fluxo próprio. Cancelar uma cobrança paga deixaria a contabilidade dizendo uma coisa e a cobrança outra.

Depois da cobrança

Webhooks

Se você configurar uma URL https no painel, avisamos ali quando a cobrança muda de estado: cobro.paid, cobro.cancelled ou cobro.expired. O corpo traz o evento (também no header X-Toki-Evento) e a cobrança completa.

JSON
{
  "id": "5b1e0c4a...",
  "evento": "cobro.paid",
  "cobro": { "id": "9cd827ca...", "estado": "paid", "monto": 15990, ... }
}

Cada entrega vai assinada no header X-Toki-Firma, na forma t=<epoch>,v1=<hmac>. O HMAC é SHA-256 sobre ${t}.${corpo} com o segredo de webhook da sua credencial. Verifique sempre: sem isso, qualquer um que conheça sua URL pode dizer que pagou.

JavaScript
import { createHmac, timingSafeEqual } from 'node:crypto';

function verificar(corpo, cabecalho, segredo) {
  const p = Object.fromEntries(cabecalho.split(',').map((x) => x.split('=', 2)));
  const t = Number(p.t);
  // O timestamp vai DENTRO do que é assinado: senão, dá para reenviar um
  // webhook antigo com um t novo e a assinatura continuaria válida.
  if (Math.abs(Date.now() / 1000 - t) > 300) return false;
  const esperado = createHmac('sha256', segredo).update(`${t}.${corpo}`).digest('hex');
  const a = Buffer.from(esperado, 'hex');
  const b = Buffer.from(p.v1 ?? '', 'hex');
  return a.length === b.length && timingSafeEqual(a, b);
}

Tratamos qualquer 2xx como entregue. Se seu servidor falhar, tentamos de novo após 5, 25, 125 e 625 minutos (cinco tentativas no total, umas dez horas), e então desistimos: o último erro fica visível no seu painel. Webhooks podem chegar repetidos, então use o `id` do aviso (também no header `X-Toki-Evento-Id`) para descartar os repetidos: é o mesmo em cada nova tentativa. E faça seu processamento idempotente sobre o id da cobrança.

Reembolsos

POST/v1/cobros/{id}/reembolsar

Devolve o dinheiro de uma cobrança paga. Sem monto, devolve tudo o que resta; com monto, devolve essa parte, e você pode chamar de novo até completar.

CampoTipoO que é
montointeiroQuanto devolver, na menor unidade da sua moeda. Omita para devolver tudo o que resta.

Seu cliente recupera 100% do que pagou, e esse valor sai inteiro da sua carteira. A comissão não volta: a cobrança já foi processada, então já estava ganha. Na prática você devolve um pouco mais do que recebeu — a diferença é a comissão daquela venda.

Vale planejar: para devolver uma venda você precisa ter o valor completo disponível, não só o que recebeu por ela. Se já sacou e não cobre, o reembolso falha com um erro dizendo quanto falta — preferimos isso a deixar um saldo negativo que alguém terá de perseguir depois.

curl -X POST https://api.toki.lat/v1/cobros/{id}/reembolsar \
  -H "Authorization: Bearer $TOKI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "monto": 5000 }'

A resposta traz reembolsado, o total devolvido até agora. E avisamos por webhook: cobro.partially_refunded enquanto sobrar algo, cobro.refunded quando tudo voltou.

O que você recebe

De cada cobrança, a Toki desconta sua comissão e liquida o resto na hora. A API não devolve isso como número fixo porque não é: depende do tipo de cobrança, do seu plano, e de qualquer tarifa acordada com você, que prevalece sobre as demais.

Vale saber: cobrar pela API paga a tarifa de venda presencial, a mais baixa do catálogo — porque você está trazendo seu próprio sistema e a Toki só entra com o meio de pagamento. Vender pelo marketplace da Toki, com catálogo e entrega, custa bem mais. As assinaturas têm tarifas próprias, e cobram a adesão diferente das renovações.

A sua, já com tudo aplicado, está no seu painel: Empresa → Dinheiro, junto ao detalhe de cada liquidação.

O digital cobrado pela loja se reparte diferente (19-set-2026). Quando a cobrança passa pela App Store ou pelo Google Play, a Toki retém 41,9 % + IVA do preço publicado e dali paga a comissão da loja: de $10.000 o vendedor recebe $5.014, seja a Apple ou o Google quem cobrou. Fora das lojas — web, cartão ou carteira — a retenção é 10 % + IVA: de $10.000 você recebe $8.810. O seu líquido não depende da loja; a diferença entre Apple e Google é absorvida pela Toki.

Assinaturas

Serviços e assinaturas

Um serviço é algo que as pessoas assinam: um plano, uma associação, uma mensalidade. Você publica pela API e gera um link para alguém assinar. Dali em diante, a cobrança se repete sozinha.

Ninguém fica assinado sem confirmar. O link não ativa nada: a pessoa vê quanto e de quanto em quanto tempo será cobrada, e confirma no app dela. Depois aparece em Dinheiro → Assinaturas, onde ela pode cancelar sem passar por você.

Publicar um serviço

POST/v1/servicios
CampoTipoO que é
nombretexto, obrigatórioO que seu cliente vê. "Plano mensal", "Mensalidade".
preciointeiro, obrigatórioNa menor unidade da sua moeda, igual a monto.
descripciontextoO que inclui.
recurrentebooleanoPadrão true. Com false fica publicado mas não aceita assinatura: para cobrar uma vez use /v1/cobros.
cadainteiroPadrão 1.
unidadtextoday, week, month, semester ou year. Padrão month.
dia_de_cobrointeiro 1–31O dia do mês em que todos são cobrados. Só com unidade month, semester ou year. Sem ele, cada pessoa é cobrada no dia em que assinou.
dia_de_semanainteiro 0–6Só com unidade week. 0 é domingo.
politica_mes_cortotextoO que fazer quando o dia não existe no mês: last, first_next ou skip. Só com dia_de_cobro acima de 28.
referencia_externatextoSeu identificador. Torna a criação idempotente.

Os campos que não correspondem à unidade escolhida são recusados, não ignorados: mandar dia_de_semana em um plano mensal devolve 400, para você não ficar achando que configurou algo.

curl -X POST https://api.toki.lat/v1/servicios \
  -H "Authorization: Bearer $TOKI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "nombre": "Plano mensal",
    "precio": 19990,
    "cada": 1,
    "unidad": "month",
    "dia_de_cobro": 1,
    "referencia_externa": "plano-mensal"
  }'
JSON
{
  "servicio": {
    "id": "b97c3820-b9ea-4352-aba4-a459aaa02015",
    "nombre": "Plano mensal",
    "descripcion": null,
    "precio": 19990,
    "moneda": "CLP",
    "recurrente": true,
    "cada": 1,
    "unidad": "month",
    "dia_de_cobro": 1,
    "dia_de_semana": null,
    "politica_mes_corto": null,
    "activo": true,
    "referencia_externa": "plano-mensal",
    "creado_en": "2026-08-25T15:50:15Z"
  }
}

Listar e editar

GET/v1/servicios

Lista todo o catálogo do comércio — inclusive o publicado pelo app, não só o criado pela API.

PATCH/v1/servicios/{id}
CampoTipoO que é
activobooleanoCom false deixa de aceitar novas assinaturas. As vigentes continuam sendo cobradas.
preciointeiroVale para quem assinar depois.
nombretexto
descripciontexto

Mudar o preço não afeta quem já assinou. O valor dela foi fixado quando aceitou; aumentá-lo daqui seria cobrar algo que ela nunca autorizou.

Gerar o link de assinatura

POST/v1/suscripciones
CampoTipoO que é
servicio_iduuid, obrigatórioO serviço a ser assinado.
referencia_externatextoSeu identificador. Torna a operação idempotente.
expira_minutosinteiroEntre 1 e 1440. Padrão 60: assinar se pensa mais do que pagar uma nota.
curl -X POST https://api.toki.lat/v1/suscripciones \
  -H "Authorization: Bearer $TOKI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "servicio_id": "…", "referencia_externa": "socio-118" }'

A resposta tem a mesma forma de uma cobrança —mesma url_checkout, mesmos estados— mais o servicio_id. Você consulta o status com GET /v1/cobros/{id} e recebe o webhook cobro.paid quando a pessoa confirma.

Identidade

Entrar com o Toki

Deixe que as pessoas entrem no seu site com a conta Toki. Elas aprovam pelo telefone com o rosto, e você recebe apenas os dados que autorizaram.

É OAuth 2.0 padrão (authorization code + PKCE), não um fluxo inventado. Use a biblioteca que você já conhece, as propriedades de segurança já foram estudadas, e quem nunca ouviu falar do Toki consegue integrar sem confiar na nossa criptografia.

Registre a sua aplicação

No painel da sua empresa, em Desenvolvedores, você registra o app e recebe um client_id e um client_secret. O segredo aparece uma única vez: guardamos só o hash, então não é que não queiramos mostrá-lo depois — nós não o temos.

Você pode integrar já com as permissões não sensíveis. Para o documento de identidade, telefone, empresas ou autorizar operações revisamos o app antes: nome, logo e responsável declarado. Um app chamado "Toki Pagamentos" com o nosso logo transformaria a tela de consentimento — justamente onde a pessoa confia — numa ferramenta de phishing.

O fluxo

Você manda a pessoa para a tela de consentimento, ela volta com um código, e o seu servidor troca esse código por um token.

GET/autorizar
Shell
https://toki.lat/autorizar
  ?client_id=seu_client_id
  &redirect_uri=https://seusite.com/oauth/callback
  &response_type=code
  &scope=perfil+email
  &state=<valor aleatório seu>
  &code_challenge=<SHA-256 do verifier, base64url>
  &code_challenge_method=S256

A pessoa vê o seu nome, o domínio real do seu site, quem responde por ele, e cada dado que você pede em linguagem humana. Se aprovar, volta ao seu redirect_uri com code e o seu state intacto.

POST/api/oauth/token
Shell
grant_type=authorization_code
code=<o codigo>
redirect_uri=https://seusite.com/oauth/callback
client_id=seu_client_id
client_secret=seu_segredo
code_verifier=<o verifier original>

Devolve access_token (vale uma hora), refresh_token (30 dias), expires_in e o scope realmente concedido — que pode ser menor do que você pediu. Leia: é a única forma de saber quais dados você tem de verdade.

GET/api/oauth/userinfo

Com Authorization: Bearer <access_token>. Cada campo só é devolvido se a permissão estiver no token. sub vem sempre: é o identificador estável da pessoa.

POST/api/oauth/revoke

Para encerrar a sessão do seu lado. Responde 200 sempre, mesmo para um token que nunca existiu: distingui-los transformaria esta rota num jeito de adivinhar tokens válidos.

Que dados você pode pedir

Peça o mínimo. Cada permissão extra é mais uma pergunta que a pessoa precisa responder antes de aprovar, e mais um motivo para não aprovar.

PermissãoO que devolveRevisão
perfilNome, foto, usuário e se o Toki verificou a identidadeNão
emailE-mailNão
rutNúmero de identidade e seu país. Vale para qualquer país: a chave se chama rut só por razões históricasSim
telefonoTelefoneSim
empresaEmpresas de que participa, com o identificador fiscal (RUT no Chile), e seu cargoSim
identidad:verificacionNível de verificação da identidade (chip, prova de vida, telefone), assinado pela TokiSim
identidad:documentoCópia do chip do documento (dados e foto) com a assinatura do emissor, para verificá-la por conta própriaSim
transacciones:autorizarAutorizar operações em nome delaSim

O que você precisa saber

  • — Use PKCE, e só com S256: plain é rejeitado. Sem PKCE a troca só é aceita com o client_secret.
  • — O redirect_uri é comparado exatamente com os que você registrou. Sem curingas: aceitá-los deixaria o código chegar a um destino que você não controla.
  • — O código dura um minuto e é de uso único. Se for trocado duas vezes, assumimos que vazou e revogamos tudo o que foi emitido para essa pessoa no seu app.
  • — Os refresh_token rotacionam: cada troca devolve um novo e invalida o anterior. Reutilizar um antigo é tratado como roubo e encerra a sessão.
  • — O client_secret nunca vai para o navegador. Se o seu app não puder guardá-lo, a troca do código funciona mesmo assim com PKCE, mas renovar exige o segredo: grant_type=refresh_token sem ele responde invalid_client. Pode ir no corpo ou em Authorization: Basic.
  • — A pessoa pode retirar o acesso quando quiser pela conta dela, e os tokens vivos são cortados junto. A sua integração tem que sobreviver a isso sem quebrar.

Mini apps

O que são

Uma mini app é um site seu que abre em tela cheia dentro do Toki, com acesso a uma ponte que deixa ela pedir coisas ao app: quem é a pessoa, a localização dela, abrir um pagamento. Você não instala nenhum SDK —a pessoa sim instala seu app pela loja da Toki, veja *Sua ficha na loja*—: o Toki injeta window.toki na sua página antes de ela carregar.

A ausência de um SDK é de propósito. Se você tivesse que instalar um pacote, cada mudança da ponte te obrigaria a atualizar; injetada, o contrato mexemos nós sem te quebrar.

Seu app roda na própria origem e só conversa com o Toki por mensagens. Nada é injetado nele: nem token, nem identificador da pessoa, nem os dados dela. Tudo o que quiser saber precisa pedir, e cada pedido passa por dois filtros — o que você declarou no manifesto e o que a pessoa autorizou.

O manifesto

Cada app se registra com um manifesto. O que importa: a clave (minúsculas e hifens, é o seu identificador estável), a url de início — https obrigatório — e as capacidades que você vai pedir.

As capacidades do manifesto são a lista completa do que o seu app pode chegar a fazer, e a pessoa a vê antes de entrar. Uma capacidade que você não declarou não pode ser pedida: a ponte responde sin_capacidad sem perguntar a ninguém. Declare o mínimo — cada permissão a mais é um motivo para não abrir o seu app.

CapacidadeO que habilita
identidadId derivado da pessoa e, com confirmação, nome e foto
compartirAbrir o compartilhador do sistema com um texto seu
ubicacionUma leitura pontual de onde ela está
pagarAbrir o pagamento de uma cobrança criada pelo seu backend
comprarIniciar uma compra com entrega: você declara o que vende e a Toki coloca o checkout
nfcLer uma etiqueta NFC comum (NDEF). Não alcança o chip de um documento
documentoRestrita. Ler o chip do documento e receber só as imagens: retrato e assinatura
abrir_appPular para outra mini app, com confirmação

⚠️ `documento` é uma capacidade restrita. Declarar não basta: um administrador da Toki concede ao seu app em particular, com um motivo registrado, e até lá a ponte responde sin_capacidad e o app não pode ser publicado. Entrega apenas as imagens —retrato e assinatura—: nunca o nome, o número de identidade nem as datas. E quem lê é a Toki: sua página não fala com o chip. A capacidade nfc, essa sim autosserviço, lê etiquetas NDEF comuns e não alcança um documento — não é bloqueio nosso: um chip de documento não publica NDEF.

Um app passa por borrador → en_revision → publicada. Só publicada aparece fora da sua equipe, e pode voltar para rechazada ou suspendida.

O app é registrado por você, no seu painel em Minha empresa → Micro apps. Ali você escreve o manifesto, salva como rascunho quantas vezes quiser e envia para revisão quando estiver pronto. Nós revisamos; não redigimos.

Cada capacidade marcada leva uma frase sua dizendo para que você precisa dela (200 caracteres). Não substitui a nossa explicação da permissão: completa. «Localização» não dá para responder com informação; «para calcular o frete até seu endereço» dá. Sem essa frase o app não entra em revisão — uma capacidade sem motivo só pode ser rejeitada.

Esse texto é mostrado à pessoa no momento em que seu app usa a permissão, não ao abrir: é quando ela pode decidir com contexto. E aparece atribuído a você, não à Toki.

A ponte toki.*

Cada método é uma função que recebe um objeto e devolve uma promessa. O objeto está congelado: não dá para substituir nem estender.

MétodoCapacidadeDevolve
toki.info()—{ v, idioma, tema, plataforma, version, capsula } — capsula: o espaço, em px CSS, que sua página deixa livre no canto superior direito
toki.identidad()identidad{ id, token, expira_en, vigencia_s } — derivado, mais um token assinado; ver abaixo
toki.perfil()identidad{ nombre, foto }, com confirmação toda vez
toki.compartir({ texto, url })compartir{ ok: true }. Você não saberá se compartilhou
toki.ubicacion()ubicacion{ lat, lng, precision }
toki.pagar({ cobroId })pagar{ abierto: true }
toki.pedido({ pedidoId })comprar{ abierto: true } — abre a folha de compra de um pedido seu
toki.abrirApp({ clave })abrir_app{ abierto: true }
toki.nfc()nfc{ id, texto, registros } — uma etiqueta NDEF comum
toki.documento()documento{ foto, firma } — imagens em base64; restrita
toki.cerrar()—fecha e volta ao Toki
JavaScript
// window.toki já existe ao carregar; não há o que esperar.
const { id } = await toki.identidad();

try {
await toki.pagar({ cobroId });
} catch (e) {
if (e.codigo === 'cancelado') return;   // a pessoa disse que não
throw e;
}

Os erros chegam como Error com um codigo estável: sin_capacidad (você não declarou), sin_permiso (a pessoa disse que não), cancelado, params, metodo_desconocido, mal_formado, interno.

Quem é a pessoa

toki.identidad() não devolve o identificador real da pessoa no Toki, e sim um derivado do par (pessoa, app). É estável para você e diferente em cada app: duas mini apps não conseguem cruzar suas bases e descobrir que falam com a mesma pessoa.

Nome e foto são outra coisa: vão por toki.perfil() e pedem confirmação toda vez, porque de dentro do app não dá para saber se o pedido nasceu de um botão que a pessoa apertou ou de um laço. Um "não" vale para a sessão inteira; insistir não abre outro diálogo.

O que o seu app nunca vai poder ler: conversas, contatos, pedidos, saldo, meios de pagamento, a sessão da pessoa nem o endereço dela. Também não pode rodar em segundo plano nem mandar notificações.

Vender com entrega

Uma cobrança serve para cobrar um valor. Um pedido serve para vender algo que precisa ser entregue: seu app declara as linhas e a Toki coloca o checkout — a lista de endereços da pessoa, a cotação do courier, o total e a confirmação. Precisa da capacidade comprar.

É uma entidade diferente dos pedidos do Merqado, de propósito: o que seu micro app vende é seu e não está no nosso catálogo. Por isso o pedido é criado por você pela API, e não escolhendo nossos produtos.

POST/v1/pedidos

Você manda linhas, nunca um total. O total é calculado por nós: subtotal menos desconto mais entrega. Se o total viajasse no corpo, quem compra poderia confirmar o pedido com frete zero.

⚠️ Desde 19-set-2026 cada linha leva a clave de um item do seu catálogo (Minha empresa → Micro apps → Itens) e o precio_unitario tem que ser o do catálogo. Uma linha sem clave é rejeitada com 400 cuerpo_invalido; com uma clave que não está no seu catálogo ativo a mensagem começa com linea_fuera_de_catalogo, e com outro preço, com precio_no_coincide (os dois também são 400 cuerpo_invalido). Se o seu preço varia por tamanho ou quantidade, declare um item por variante: mudar o catálogo não exige publicar nada.

O preço do catálogo nem sempre está no item. Se o item declara o valor dele, é esse que manda e nada mais é consultado. Se ficar vazio e o item pender de um serviço do seu catálogo de Mercado, o preço do catálogo é o desse serviço — e só enquanto o serviço estiver ativo: um arquivado não empresta o dele. Se não houver nenhum dos dois, o item não tem preço e a linha responde precio_no_coincide seja qual for o número enviado; não é «preço a combinar», é um item que não pode ser vendido.

A consequência prática: um item que herda muda de preço quando alguém edita o serviço, sem tocar na micro app nem publicá-la de novo. Se o seu backend guarda o preço numa cópia própria, vai começar a mandar o antigo e a receber precio_no_coincide sem ter mudado nada. Leia o preço vigente no painel (Minha empresa → Micro apps → Itens, que mostra o preço e de onde ele vem), ou declare o valor no item para que não dependa do serviço.

curl https://api.toki.lat/v1/pedidos \
-H "Authorization: Bearer $TOKI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "app": "foto-trama",
  "items": [{ "nombre": "10 cópias 10x15", "cantidad": 1, "precio_unitario": 6990, "clave": "copias-10x15" }],
  "entrega": { "tipo": "despacho", "origen_comuna": "PROV", "bulto": { "peso_g": 400 } },
  "referencia_externa": "pedido-1042"
}'

As três formas de entregar

`entrega.tipo`O que aconteceO que você precisa mandar
despachoA pessoa escolhe o endereço e nós cotamos o frete com o courierorigen_comuna e, se possível, bulto
retiroA pessoa escolhe um dos seus depósitos com retirada habilitadanada: os depósitos saem do seu perfil
digitalNão há nada para entregarnada

⚠️ origen_comuna é o código de comuna da Chilexpress (PROV, STGO), não o nome. Com o nome a cotação falha só na confirmação, longe desta chamada.

Para retiro você precisa de pelo menos um depósito com retirada habilitada em Minha empresa → Depósitos. É de lá que sai o custo da retirada: não vai no corpo, igual ao frete.

Se você aceita mais de uma, não pergunte você. Envie entrega.tipos: ["despacho", "retiro"] e a folha da Toki pergunta à pessoa como ela quer receber, mostrando o frete estimado e o custo da retirada de cada opção. entrega.tipo passa a ser a proposta primeiro. Perguntar na sua página é fazer checkout à mão: você precisaria conhecer depósitos e custos, e criar um pedido diferente por resposta. digital não se combina com as outras —seria deixar quem paga escolher o trilho de cobrança—, e se você oferecer despacho, origen_comuna continua obrigatório.

E depois, no telefone

Com o id que devolvemos, sua página abre o checkout da Toki: await toki.pedido({ pedidoId: id }). Ali a pessoa escolhe endereço ou depósito, vê o total já cotado e paga. O valor não viaja nessa chamada — o pedido já tem preço. Se o pedido venceu, não está mais pendente ou é de outro app, responde params com um detalle que diz isso (pedido_vencido, pedido_pagado, pedido_de_otra_app…).

GET/v1/pedidos/{id}

Esta é a sua fonte de verdade, não o que a ponte devolve: aquilo é o seu próprio código contando como foi, num aparelho que você não controla. Aqui vêm o estado, o despacho cotado, o total e —se for retirada— entrega.retiro com nome, endereço e comuna do lugar onde a pessoa vai, para você poder avisar.

O que você nunca vai receber é o endereço de quem compra. Ela escolhe na nossa folha e ele serve para enviar; se a Toki retira no seu depósito e envia, você não precisa dele. É esse o ponto.

O aviso chega como pedido.paid com o id do pedido —não o da cobrança, que você nunca viu. Também existem pedido.cancelled, pedido.expired, pedido.refunded e pedido.partially_refunded. São assinados como os de cobranças.

  • — Um pedido vive 30 minutos por padrão (expira_minutos). Depois disso fica expirado e é preciso criar outro.
  • — referencia_externa é a sua idempotência: a mesma referência com a mesma credencial devolve o pedido já criado, não um novo. Se aquele pedido já venceu, mande uma referência nova.
  • — A clave de cada linha (obrigatória, ver acima) é também o que baixa o estoque e liga o pedido ao item declarado. Sem estoque suficiente, responde 409 estado_invalido.
  • — Um pedido é de um só vendedor. Um carrinho com dois micro apps é outro problema e ainda não existe.

Resgatar um pedido pago

Se o que você vende custa dinheiro para gerar —uma imagem de um modelo, um relatório, um minuto de vídeo— você precisa saber que aquele pedido não foi resgatado antes. Manter essa conta do seu lado é mais difícil do que parece: em memória não serve (duas instâncias não se veem) e no seu banco é um contador que precisa ficar consistente com o que outro cobrou. Como quem cobrou foi a Toki, a conta é da Toki.

POST/v1/pedidos/{id}/consumir

Responde ok só se o pedido estiver pago e ainda tiver um uso, e o desconta na mesma chamada. A referencia do corpo é sua e torna o resgate idempotente: se a resposta se perder depois de contarmos o uso, repetir com a mesma referência devolve o mesmo —com repetido: true— em vez de gastar outro. Sem isso, um timeout cobra duas vezes de quem comprou uma.

Shell
curl https://api.toki.lat/v1/pedidos/$PEDIDO/consumir \
  -H "Authorization: Bearer $TOKI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "referencia": "geracao-1" }'
  • — 404 — o pedido não existe, ou não é seu.
  • — 409 — ainda não está pago (a mensagem diz em que estado), ou já usou todos os resgates.
  • — Um pedido concede um resgate por padrão.

Usos por comprador

O de cima conta os resgates de UM pedido. Se você vende pacotes —1 foto por $790, 10 por $4.990— o que a pessoa precisa é outra coisa: usos DELA. Compra dois pacotes de 10, tem 20, e gasta quando quiser sem lembrar com qual pedido pagou qual. Isso não vai pela API: quem declara é o seu catálogo, com usos_por_compra no item, e a Toki credita sozinha quando o pedido fica pago. Um item sem usos_por_compra —um quadro impresso— não deixa usos.

POST/v1/apps/usos/consumir
Shell
curl https://api.toki.lat/v1/apps/usos/consumir \
  -H "Authorization: Bearer $TOKI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "x-toki-identidad: $TOKI_IDENTIDAD" \
  -d '{ "comprador": "'$COMPRADOR'", "item": "fotos-10", "referencia": "foto-9f3ac1-v1", "entregable": { "url": "https://…" } }'
GET/v1/apps/usos?comprador={comprador}&item={item}

comprador é o pseudônimo que você já conhece: o mesmo que chega com a capacidade identidad e em pedido.comprador. Você nunca vai receber quem é a pessoa. O GET serve para pintar «restam 7» antes de ela apertar o botão, mas NÃO é um controle: entre ler e entregar ela pode gastar de outro celular. Quem decide é o POST, que desconta e responde na mesma chamada. Ele desconta unidades (de 1 a 1000, padrão 1), e com uma credencial de teste não gasta nada: responde com simulado: true.

⚠️ A referencia tem que identificar O QUE você entrega, nunca ser uma constante. Repeti-la responde ok com repetido: true todas as vezes que chamar — é o que te salva de um timeout, e o que a torna inútil como limite se for sempre a mesma. Por isso guardamos o seu entregable e devolvemos o guardado na repetição: assim você não paga de novo ao seu provedor para gerar a mesma coisa. Constantes óbvias («descarga», «uso», «default») são rejeitadas.

  • — 404 — essa pessoa não tem usos desse item, ou o item não é da sua credencial.
  • — 409 `sin_usos` — não restam suficientes. A resposta traz disponibles.
  • — 400 `referencia_debil` — sua referência é uma constante óbvia, tem menos de 8 caracteres ou começa com pedido: ou reembolso:, que a Toki usa.
  • — Reembolsar o pedido devolve os usos dele. Se a pessoa já gastou algum, o reembolso não sai sozinho: vocês dois resolvem.

Identidade assinada

O comprador é a sua própria página que te passa, então sozinho ele não prova nada: quem souber o pseudônimo de outra pessoa queimaria os usos dela. Por isso toki.identidad() te devolve, junto com o pseudônimo, um token assinado pela Toki. Você reenvia igualzinho no cabeçalho x-toki-identidad e nós verificamos que aquele pseudônimo é de quem diz ser. O pseudônimo continua sendo a chave do saldo: o token prova de quem é, não o substitui.

JavaScript
const { id, token, expira_en } = await toki.identidad();
// id     → o pseudônimo (64 hex). É a chave do saldo dela.
// token  → credencial de 15 min. Mande ao SEU backend, nunca numa URL.
await fetch('/meu-backend/trabalho', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ persona: id, identidad: token }),
});

⚠️ O token é uma credencial, não um dado. Não escreva em logs, não coloque numa URL nem num parâmetro de consulta —fica no histórico, no Referer e nos registros de qualquer servidor por onde passar— e não guarde além do vencimento.

  • — Está amarrado ao seu app. É assinado com uma chave que só existe para o seu app: um token de outro mini app não valida aqui, nem o seu lá.
  • — Não leva o uid da pessoa nem nada que permita cruzá-la com outro app. Só o pseudônimo que você já conhecia.
  • — Vive 15 minutos. Chamar toki.identidad() de novo te dá outro sem perguntar nada à pessoa. Peça antes de cada trabalho longo.
  • — 401 `identidad_vencida` — venceu no meio. Renove e repita com a mesma `referencia`: é idempotente, então não gasta um uso a mais nem te faz gerar de novo.
  • — 401 `identidad_requerida` — seu app já fechou o modo antigo e você não mandou token. 401 `identidad_no_coincide` — esse token é de outra pessoa, não a do comprador. 401 `identidad_invalida` — o resto.
  • — Enquanto o seu app não exigir, é opcional —mas se mandar, é verificado do mesmo jeito—. A resposta traz consumo.identidad_firmada para você ver em que modo está. Se token vier null, essa pessoa está numa versão da Toki anterior a isto.

Sua ficha na loja

As pessoas descobrem seu app numa ficha e o instalam antes de abrir. Você monta a ficha em Minha empresa → Micro apps, com estes campos além do manifesto (nenhum é obrigatório para publicar, mas uma ficha sem capa nem capturas convence pouco):

CampoO que é
portadaImagem horizontal 16:9, com pelo menos 1280 px de largura. É a imagem grande da ficha e a prévia ao compartilhar o link.
capturasAté 8 capturas verticais (9:19,5 ou 9:16), com pelo menos 1080 px de largura, na ordem que você escolher.
descripcion_largaO texto da ficha, até 4000 caracteres. descripcion continua sendo a linha curta do catálogo.
novedadesO que mudou nesta versão, até 1000 caracteres.
version_publicaA versão que a pessoa vê: 1, 1.4 ou 1.4.2. A version numérica é a Toki que sobe a cada edição.
sitio_soporteURL https de ajuda.
correo_soporteE-mail de contato.
politica_privacidadURL https. Muito recomendável se você pede algo sensível (identidade, localização, NFC, documento, pagamentos).

As imagens ficam na Toki, como o ícone: você as envia pelo painel (ou cola uma URL e a Toki a copia uma vez), elas são validadas pelos bytes —PNG, JPEG ou WEBP, nada de SVG, até 4 MB— e recortadas ao centro numa medida fixa. Uma URL do seu servidor nunca é guardada: se o seu servidor cair, sua ficha continua inteira.

ImagemAceitaFica em
Capa16:9 (±2 %), ≥ 1280 px de largura1920×1080, ou 1280×720 se o original não der
Captura9:19,5 ou 9:16 (±3 %), ≥ 1080 px de largura1080×2340 ou 1080×1920

A ficha também mostra, sem você escrever mais nada: sua empresa e se está verificada, quais permissões seu app pede e quando, quais dados usa —derivado das suas capacidades e da frase que você escreveu para cada uma— e o que você vende com o preço.

  • — Essenciais, ao instalar. As capacidades que você marca como essenciais (só identidad e ubicacion) são aceitas com um toque ao instalar, listadas com a sua frase. Sem elas seu app não abre.
  • — O resto, no contexto. O restante é pedido na primeira vez que seu app usa, e pagamentos, compras, compartilhar e abrir outro app são confirmados cada vez. Continua valendo não pedir permissões ao abrir.
  • — Se você adicionar uma essencial numa versão nova, quem já instalou é perguntado antes de abrir: nunca é concedida em silêncio. Adicione só se seu app realmente não funciona sem ela.
GET/v1/apps/{clave}

A mesma ficha em JSON, sem credencial: serve para mostrar no seu site como as pessoas veem você na Toki. Traz nombre, portada, capturas, desarrollador (com verificado), permisos (com esencial, sensible, momento e a sua declaracion), datos, compras (com precio_clp), version_publica, novedades e soporte. Só apps publicados: o resto é 404. Fica em cache por 5 minutos.

Regras que não se negociam

  • — Não peça permissões ao abrir. A Toki mostra a folha no momento em que seu app usa a capacidade, então chamar toki.identidad() no arranque pede que a pessoa decida sobre algo que ainda não fez — e a resposta razoável a isso é «não». Peça na ação que precisa. Na revisão isso é olhado.
  • — Só `https`, e só a sua origem. Um link para outro site abre no navegador do sistema, fora do Toki; um iframe de outro domínio não carrega. Se carregasse, aquele quadro teria as permissões do seu app.
  • — O valor nunca viaja pela ponte. A cobrança é criada pelo seu backend contra a API com a sua credencial, e toki.pagar só abre uma que já existe com o preço dela. Um preço que chega da página é um preço que se edita com o console aberto.
  • — Você só pode abrir cobranças suas. toki.pagar verifica que a cobrança pertence ao perfil comercial dono do app. A cobrança de outro comércio responde params.
  • — Os cookies de terceiros estão cortados dentro do contêiner. A sua página mantém os dela; o que deixa de funcionar é o pixel de um terceiro seguindo a pessoa entre mini apps.
  • — O Toki não passa para a sua página os cookies nem a sessão dele, e a sua página não acessa file:// nem pode abrir janelas sem um gesto.

Ferramentas

WooCommerce

Se sua loja é WooCommerce, você não precisa escrever código: existe um plugin que faz tudo isso por você.

Baixar o plugin
  • 1. Instale em Plugins → Adicionar novo → Enviar plugin.
  • 2. Vá em WooCommerce → Configurações → Pagamentos → Toki.
  • 3. Cole sua credencial de teste e deixe o modo de teste ligado.
  • 4. Copie a "URL para avisos" que aparece ali e cole na sua credencial da Toki, junto com o segredo de assinatura.
  • 5. Faça um pedido completo. POST /v1/cobros/{id}/simular-pago dá como pago sem movimentar dinheiro.
  • 6. Quando funcionar, cole a credencial de produção e desligue o modo de teste.

Faz cobranças, reembolsos totais e parciais pelo próprio pedido, e verifica a assinatura de cada aviso. O pedido é marcado como pago pelo webhook, nunca porque o cliente voltou à loja — voltar não prova que pagou.

Ao salvar, o plugin pergunta à Toki em que moeda você cobra e avisa se não bate com a da sua loja. A Toki não converte moedas, então nesse caso o método não aparece no checkout em vez de cobrar um número na moeda errada.

Modo de teste

Crie uma credencial em modo teste no seu painel e desenvolva com ela sem movimentar um centavo. A chave é diferente —começa com tk_test_— para não se confundir com a de produção, nem num log nem num arquivo de configuração.

Uma cobrança de teste não pode ser paga com dinheiro real, e uma real não pode ser dada como paga simulando. Os dois cadeados estão no banco, não nesta documentação: não dependem de ninguém tomar cuidado.

Dar uma cobrança de teste como paga

POST/v1/cobros/{id}/simular-pago

Marca a cobrança como paga e dispara seu webhook, sem escrever um único lançamento contábil. É assim que você testa sua tela de obrigado e seu tratamento do evento antes de cobrar de alguém.

curl -X POST https://api.toki.lat/v1/cobros/{id}/simular-pago \
  -H "Authorization: Bearer $TOKI_TEST_KEY"

Cobranças de teste vêm com es_prueba: true na resposta. Se sua integração as vê em produção, você subiu a credencial errada.

Referência

Erros

Todos os erros têm a mesma forma. O code é estável e pensado para você comparar no seu código; a mensaje é para você ler.

JSON
{ "error": { "code": "credencial_invalida", "mensaje": "..." } }
codeHTTPO que aconteceu
sin_credencial401Falta o header Authorization.
credencial_invalida401Chave, segredo ou IP de origem que não batem. Não dizemos qual, de propósito.
sin_alcance403Falta à sua credencial o escopo que essa rota exige. A mensagem diz qual falta e quais você tem — ver «Escopos».
cuerpo_invalido400Falta um campo ou ele tem o tipo errado.
no_encontrado404Esse id (cobrança, pedido, serviço…) não existe ou não é desta credencial. As duas coisas recebem a mesma resposta de propósito.
estado_invalido409Existe e é seu, mas não está em condições: a cobrança não está mais pendente, o saldo não cobre o reembolso, o pedido não está pago…
sin_usos409Essa pessoa não tem mais usos desse item. Traz disponibles — ver «Usos por comprador».
referencia_debil400A referencia de um consumo é uma constante, tem menos de 8 caracteres ou usa um prefixo reservado.
identidad_requerida, identidad_invalida, identidad_vencida, identidad_no_coincide401Falta o token de x-toki-identidad ou ele não vale — ver «Identidade assinada».
demasiadas_solicitudes429Você passou de um limite: 120 chamadas por minuto por credencial, 600 por IP, ou 30 mensagens por minuto num canal.
error_interno500Falha nossa. Repetir é seguro se você mandar referencia_externa.

Antes de ir para produção

  • — Guarde o segredo onde guarda os outros segredos, nunca no código nem no front.
  • — Configure a lista de IPs. É o cadeado que continua servindo se o segredo vazar.
  • — Mande sempre referencia_externa: é o que impede cobrar duas vezes quando a rede falha no meio.
  • — Verifique a assinatura de cada webhook, e não decida nada com um postMessage.
  • — Tenha um plano para quando o webhook não chegar: consulte o status antes de entregar.