`), e uma chave pode ter um perfil padrão.
## Custos e controle de gasto
* Cada chamada custa o mesmo que a operação equivalente na API, e as leituras medidas entram na mesma [franquia diária](/cobranca-e-limites#franquia-diaria).
* Toda resposta traz o custo em `_meta["tech.respawn/cost"]`: créditos cobrados, estimativa (em dry-run), saldo depois e quanto resta do limite do dia.
* `simulate=true` na URL faz todas as ferramentas que cobram devolverem só a estimativa. É o jeito de testar um agente sem gastar.
* Passar do limite diário da chave responde `billing.spend_limit_reached`; passar do teto por chamada (`max_call_credits`) responde `billing.call_limit_exceeded`.
* O **botão de pânico** no dashboard desliga o acesso de todos os agentes da conta na hora (`mcp.agents_disabled`).
* O extrato do dashboard separa o gasto por canal (API, MCP, dashboard) e por chave.
## Idempotência
Agentes repetem chamadas com frequência. Ferramentas que criam ou alteram algo aceitam `idempotency_key`; sem ele, uma chamada idêntica (mesma chave, ferramenta e argumentos) em até devolve o resultado anterior sem executar nem cobrar de novo (`_meta["tech.respawn/replayed"]`).
## Limites
| Limite | Valor |
| --------------------------- | --------------------------------------------------------------------------- |
| Requisições | por minuto por chave, somadas às da API |
| Corpo da requisição | |
| Resultado de uma ferramenta | |
| Chamadas de `tools_search` | por minuto |
## Erros
Erros de negócio voltam como resultado da ferramenta, com uma mensagem que diz ao agente o que fazer e o código em `_meta["tech.respawn/error"]`. Erros da conexão voltam como HTTP, no formato `{"error": {"code", "message"}}`.
Os erros das ferramentas são os mesmos da API; veja [Códigos de erro](/erros).
# Autenticação
## Token de API
Todas as rotas em `/v1/tools/*` exigem um **token de API**, criado no [dashboard](https://dashboard.respawntech.dev) em **APIs**.
| Forma | Exemplo |
| ----------------------------- | ------------------------------------ |
| Header dedicado (recomendado) | `X-API-Token: rsk_live_...` |
| Authorization | `Authorization: ApiKey rsk_live_...` |
| Authorization (alternativo) | `Authorization: Token rsk_live_...` |
Guarde o token na variável de ambiente `RESPAWN_API_TOKEN`, crie um token por integração com os **escopos mínimos** necessários e revogue os que não usar mais.
## Requisitos da conta
Antes de qualquer chamada às ferramentas, a conta precisa ter:
* **E-mail verificado** — caso contrário, `auth.email_not_verified` (403).
* **Saldo não negativo** — conta suspensa retorna `billing.user_bankrupt` (402).
* **Créditos suficientes** — operações pagas sem saldo retornam `billing.insufficient_credits` (402).
## Escopos
Cada rota aceita um conjunto de escopos; basta o token ter **um** deles. Se faltar permissão, a API retorna `scope.missing` (403) com `details.required` e `details.provided`. Os escopos aceitos por cada endpoint aparecem no card do endpoint.
Rotas de leitura de PDF aceitam apenas `tools:pdf:read` ou `tools:manage`. O escopo genérico `tools:read` e o `tools:pdf:manage` **não** dão acesso à leitura de PDF.
### Combinações recomendadas
| Caso de uso | Escopos sugeridos |
| -------------------------------------------- | ------------------------------------------------------------------------ |
| Gerar e gerenciar QR codes | `tools:qr:dynamic:manage` |
| Painel somente leitura de QRs | `tools:qr:dynamic:read` |
| Pipeline de PDF | `tools:pdf:manage` + `tools:pdf:read` |
| Verificar URLs antes de exibir | `tools:safety:read` |
| Assistente que busca na base de conhecimento | `tools:rag:query` |
| Enviar e manter documentos da base | `tools:rag:manage` |
| [Agente de IA (MCP)](/agentes-de-ia) | `mcp:use` + os escopos das ferramentas que ele vai usar + `account:read` |
| Integração completa | `tools:manage` (mais simples, menos seguro) |
## Controles da chave
Além dos escopos, cada token pode ter controles configurados no dashboard. Eles valem em qualquer canal, na API e no [MCP](/agentes-de-ia):
* **Expiração**: depois da data, o token para de funcionar (`auth.unauthorized`).
* **Limite diário de gasto**: uma cobrança que passaria do limite do dia é recusada com `billing.spend_limit_reached` (402), com `details.limit`, `details.spent_today` e `details.required`. O contador zera à meia-noite UTC.
* **Restrição por base de conhecimento**: o token só enxerga as bases escolhidas; as outras respondem `not_found`, e ele não pode criar bases novas (`forbidden`).
* **Modelos prontos**: ao criar a chave, "Somente leitura", "Agente de suporte" e "Agente completo" preenchem escopos e controles de uma vez.
Expiração, limite e bases podem ser alterados depois; os escopos, não. Para mudar escopos, crie outra chave.
## Erros de autenticação
## Dashboard vs. token de API
* **Dashboard** — a navegação no painel não consome a franquia diária nem debita leituras medidas.
* **Token de API** — leituras medidas compartilham a [franquia diária](/cobranca-e-limites#franquia-diaria) de requisições ( em contas premium). Depois disso, cada leitura debita créditos.
## Exemplo
Criar um QR com dry-run para validar o token, os escopos e o custo sem debitar nada:
# Cobrança e limites
## Modelo de créditos
* O saldo é exibido em **créditos** no dashboard, e todos os valores desta documentação estão em créditos.
* Operações pagas debitam na confirmação — nunca no dry-run.
* As regras de preço de cada ferramenta ficam na página da ferramenta: [QR Dinâmico](/ferramentas/qr-dinamico#custos), [PDF Convert](/ferramentas/pdf-convert#custos), [Base de conhecimento](/ferramentas/base-de-conhecimento#custos), [Áudio](/ferramentas/audio#custos).
* Cada token pode ter um [limite diário de gasto](/autenticacao#controles-da-chave): passar dele responde `billing.spend_limit_reached`.
Custos de criação podem somar adicionais (slug customizado, design, retenção de PDF…). Faça um dry-run com o payload real para ver o total exato.
## Dry-run e preview de custo
| Header | Efeito |
| ---------------------- | -------------------------------------------------- |
| `X-Dry-Run: true` | Valida e calcula o custo sem persistir nem debitar |
| `X-Preview-Cost: true` | Alias de `X-Dry-Run` |
| `X-Include-Cost: true` | Inclui `meta.cost` na resposta |
Dry-run é aceito nas operações pagas marcadas no card de cada endpoint e nas **leituras medidas** (exceto downloads). A resposta é `200` e nada é persistido nem debitado.
As rotas de [Áudio](/ferramentas/audio#dry-run) pedem o dry-run com `?dry_run=true` na query, não com o header. O áudio é cobrado por minuto de áudio, por 1.000 caracteres ou por segundo gerado, e o valor da transcrição, da fala e dos demais jobs é devolvido se o motor falhar.
### Orçamento de uma operação paga
Em `POST` de criação e conversão, `data` traz o orçamento item a item:
```json
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"total_cost_credits": 15000,
"total_cost_units": 15000,
"items": [
{ "description": { "key": "billing.pdf.convert_base" }, "cost_credits": 10000, "type": "base" },
{ "description": { "key": "billing.pdf.retention.7" }, "cost_credits": 5000, "type": "storage" }
],
"is_monthly_recurring": false,
"currency": "CREDITS"
}
}
```
Apesar do nome, `total_cost_credits` e `cost_credits` do orçamento (e `cost_credits` das conversões de PDF) vêm em **unidades internas**: divida por 10.000 para obter créditos. No exemplo, 15.000 unidades = 1,5 crédito.
No QR Dinâmico o orçamento também traz `slot_usage` (uso de slots e se a criação abrirá um novo slot pago) e `recurring_cost`.
### Preview de uma leitura medida
Em leituras medidas, o dry-run não executa a consulta — mostra apenas a posição na franquia e se a leitura seria cobrada:
```json
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"operation": "qr_dynamic.analytics",
"metering": {
"within_quota": true,
"quota_limit": 2000,
"quota_used": 125,
"quota_remaining": 1875,
"cost_units": 300,
"would_charge": false
}
},
"meta": {
"cost": {
"units": 300,
"credits": 0.03,
"charged": false,
"within_quota": true,
"quota_limit": 2000,
"quota_used": 125,
"quota_remaining": 1875
}
}
}
```
Com `X-Include-Cost: true` numa leitura real, o mesmo objeto `meta.cost` acompanha a resposta normal (`charged: true` quando a leitura foi cobrada).
## Idempotência
Envie `X-Idempotency-Key` (UUID recomendado) em qualquer rota de ferramentas:
* a chave vale por ****;
* mesma chave + mesmo payload → a resposta original é reaproveitada, sem nova cobrança;
* mesma chave + payload diferente → `idempotency.conflict` (409).
Use sempre em `POST` de criação e conversão para que retries de rede não cobrem duas vezes.
Exceção: as rotas da [Base de conhecimento](/ferramentas/base-de-conhecimento#limites) ignoram o header, porque já são idempotentes (conteúdo repetido não é reindexado nem cobrado).
## Rate limit
Cada token de API pode fazer ** requisições por minuto** nas rotas `/v1/tools/*`. Toda resposta informa a situação:
```http
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1712345678
```
Ao exceder, a API retorna `rate_limit.exceeded` (429). Aguarde até `X-RateLimit-Reset` antes de reenviar.
## Repetir com espera (retry)
Repita só o que pode dar certo na tentativa seguinte:
* **`429`**: espere até o horário de `X-RateLimit-Reset` (segundos Unix).
* **`5xx` e falhas de rede**: espere cada vez mais, com um pouco de aleatoriedade (*backoff* exponencial com *jitter*): cerca de 1 s, 2 s, 4 s… até 30 s, no máximo 5 tentativas.
* **Demais `4xx`** (validação, escopo, saldo): não repita. A resposta não muda até você corrigir a requisição.
Em `POST` que cria ou cobra algo, envie **a mesma** `X-Idempotency-Key` em todas as tentativas: se a primeira chegou e só a resposta se perdeu, a repetição devolve o resultado original sem cobrar de novo.
```js
// Repete 429, 5xx e falhas de rede; devolve na hora os demais erros.
async function withRetry(send, attempts = 5) {
for (let attempt = 1; ; attempt++) {
let response;
try {
response = await send();
} catch (error) {
if (attempt === attempts) throw error; // falha de rede
}
if (response && response.status !== 429 && response.status < 500) return response;
if (attempt === attempts) return response;
const reset = Number(response?.headers.get('X-RateLimit-Reset'));
const wait =
response?.status === 429 && reset
? Math.max(0, reset * 1000 - Date.now())
: Math.min(2 ** (attempt - 1) * 1000, 30_000) * (0.5 + Math.random());
await new Promise((resolve) => setTimeout(resolve, wait));
}
}
const key = crypto.randomUUID(); // a mesma chave em todas as tentativas
const response = await withRetry(() =>
fetch(url, {
method: 'POST',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'X-Idempotency-Key': key,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
}),
);
```
```python
import os, random, time, uuid
import requests
# Repete 429, 5xx e falhas de rede; devolve na hora os demais erros.
def with_retry(send, attempts=5):
for attempt in range(1, attempts + 1):
try:
response = send()
except (requests.ConnectionError, requests.Timeout):
if attempt == attempts:
raise
response = None
if response is not None and response.status_code != 429 and response.status_code < 500:
return response
if attempt == attempts:
return response
reset = response.headers.get("X-RateLimit-Reset") if response is not None else None
if response is not None and response.status_code == 429 and reset:
wait = max(0, int(reset) - time.time())
else:
wait = min(2 ** (attempt - 1), 30) * (0.5 + random.random())
time.sleep(wait)
key = str(uuid.uuid4()) # a mesma chave em todas as tentativas
response = with_retry(lambda: requests.post(
url,
json=payload,
headers={"X-API-Token": os.environ["RESPAWN_API_TOKEN"], "X-Idempotency-Key": key},
timeout=30,
))
```
## Franquia diária de leituras
Leituras feitas com **token de API** (listagens, status, analytics, downloads…) entram em uma franquia diária compartilhada entre todas as ferramentas:
| Item | Valor |
| ----------------------- | -------------------------------------- |
| Leituras grátis por dia | **** |
| Contas premium | **** |
| Reset | |
Toda leitura medida devolve a posição na franquia:
```http
X-API-Quota-Limit: 2000
X-API-Quota-Used: 847
X-API-Quota-Remaining: 1153
```
Requisições feitas pelo [dashboard](https://dashboard.respawntech.dev) **não** entram na franquia nem são cobradas.
### Leituras medidas
Depois de esgotar a franquia, cada leitura via API debita:
Operações de escrita sem custo próprio (`PATCH`, ativar, desativar, `DELETE`) e o dry-run de criação são sempre gratuitos.
A franquia cobre a maioria das integrações. Para acompanhar mudanças de estado, prefira [webhooks](/webhooks) em vez de consultar o status repetidamente.
## Timeouts
* Maioria das rotas: ****.
* Downloads de arquivo: ****.
# Códigos de erro
## Resposta de sucesso
```json
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {},
"meta": {}
}
```
| Campo | Descrição |
| ---------- | ------------------------------------------------ |
| `success` | `true` em respostas OK |
| `trace_id` | ID de correlação (também no header `X-Trace-Id`) |
| `data` | Recurso ou resultado |
| `meta` | Opcional — custo (`meta.cost`), paginação etc. |
Com `X-Include-Cost: true` ou em dry-run, `meta` traz o custo da operação — veja os formatos em [Dry-run](/cobranca-e-limites#dry-run). Listagens trazem a paginação dentro de `data` (`total` e, no PDF, `page` e `page_size`), não em `meta`.
## Formato de erro
```json
{
"success": false,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"error": {
"code": "billing.insufficient_credits",
"message": "Créditos insuficientes para esta operação.",
"details": { "required": 100, "available": 50 }
}
}
```
| Campo | Descrição |
| --------------- | ----------------------------------------------------------------------------------------------- |
| `error.code` | Identificador estável — trate erros por ele, nunca pela `message` |
| `error.message` | Texto legível; pode mudar entre versões |
| `error.details` | Contexto estruturado opcional; em `validation.failed`, `details.fields` traz os erros por campo |
| `trace_id` | Informe em tickets de suporte |
## Erros comuns a todas as ferramentas
### Autenticação e permissões
### Créditos e conta
### Requisição e limites
### Agentes de IA (MCP)
### Plataforma
### Vídeo
## Erros por ferramenta
Jobs assíncronos (PDF) também expõem `error_code` no próprio recurso quando `status` é `failed`; códigos marcados como **assíncrono** aparecem apenas ali.
## Boas práticas
1. Trate `402` com um fluxo de recarga no seu sistema.
2. Em `429`, respeite `X-RateLimit-Reset` antes de tentar de novo.
3. Use `X-Dry-Run` para capturar `validation.failed` sem custo.
4. Repita apenas erros `5xx` e de rede, sempre com a mesma `X-Idempotency-Key`.
5. Registre o `trace_id` de toda resposta com erro.
# Documentação da API
## Ferramentas
## Primeiros passos
### Crie um token de API
No [dashboard](https://dashboard.respawntech.dev), abra **APIs** e crie um token com os escopos da ferramenta que vai usar. Guarde-o na variável de ambiente `RESPAWN_API_TOKEN`. Veja [Autenticação](/autenticacao).
### Estime o custo com dry-run
Envie `X-Dry-Run: true` antes de uma operação paga para validar o payload e ver o custo sem debitar créditos. Veja [Cobrança e limites](/cobranca-e-limites#dry-run).
### Faça a chamada
Todas as rotas de ferramentas usam o prefixo `https://api.respawntech.dev/v1/tools`. Cada endpoint traz exemplos prontos em cURL, JavaScript e Python.
## Headers comuns
| Header | Valor | Efeito |
| ------------------- | -------------- | ---------------------------------------------------------------------- |
| `X-API-Token` | `rsk_live_...` | Autenticação (recomendado) |
| `X-Dry-Run` | `true` | Simula a operação sem persistir nem debitar |
| `X-Include-Cost` | `true` | Inclui `meta.cost` na resposta |
| `X-Idempotency-Key` | UUID | Evita cobrança duplicada em reenvios () |
Respostas trazem `X-Trace-Id` (use em tickets de suporte), `X-RateLimit-*` e, em leituras medidas, `X-API-Quota-*`.
## Guias
# Usar com IA
Assistentes de IA escrevem integrações melhores quando consultam a documentação em vez de adivinhar. Há três formas de entregar estas páginas a um agente, da mais completa à mais simples: o **MCP de documentação**, o **llms.txt** e as **páginas em Markdown**.
## Dois servidores MCP
| | MCP de documentação | [MCP da conta](/agentes-de-ia) |
| ------------ | ------------------------------------------------------ | --------------------------------------------------------------------------------- |
| Para quê | Responder perguntas sobre a API enquanto você programa | Usar as ferramentas com a sua conta (criar QR, converter PDF, transcrever áudio…) |
| URL | | |
| Autenticação | Nenhuma | Token de API com `mcp:use` |
| Custo | Grátis | O mesmo da API |
| Acesso | Só leitura da documentação pública | Age na conta, com escopos e limite de gasto |
Os dois podem ficar conectados ao mesmo tempo: um agente consulta a documentação e depois usa o MCP da conta para executar.
O MCP de documentação está sendo liberado aos poucos. Enquanto ele não estiver ativo, responde `404` com o código `mcp.disabled`. O llms.txt e as páginas em Markdown já funcionam.
## Conexão
| | |
| ------------ | ------------------------------------------------------------------------- |
| URL | |
| Transporte | Streamable HTTP |
| Autenticação | Nenhuma (não envie token) |
| Custo | Grátis |
| Limite | requisições por minuto por IP |
| Idioma | Parâmetro `lang` opcional em cada ferramenta: `pt-br`, `en`, `es` ou `fr` |
As respostas vêm do mesmo conteúdo deste site, incluindo os preços em vigor.
## Configure o cliente
No **Claude.ai** e no **ChatGPT**, adicione um conector personalizado (custom connector) e informe só a URL . Não é preciso login nem token. Clientes que só falam stdio usam a ponte `mcp-remote`, como no último exemplo.
## Ferramentas
Um fluxo típico: `docs_search` com a pergunta, `docs_get_page` na seção encontrada e `docs_get_endpoint` ou `docs_get_pricing` para os detalhes exatos antes de escrever o código.
## Sem MCP: llms.txt e Markdown
Para agentes que só leem a web:
* [`/llms.txt`](/llms.txt): índice no formato [llmstxt.org](https://llmstxt.org), com cada página em cada idioma e o link para a versão em Markdown.
* [`/llms-full.txt`](/llms-full.txt): todas as páginas em um único arquivo de texto.
* Páginas em Markdown: `/md/.md` em português e `/md//.md` nos outros idiomas, por exemplo [`/md/webhooks.md`](/md/webhooks.md) ou [`/md/en/webhooks.md`](/md/en/webhooks.md). A página inicial é `/md/index.md`.
* [`/ai/bundle.json`](/ai/bundle.json): a documentação inteira em JSON estruturado (seções, ferramentas, endpoints, preços, erros, escopos e limites). É a base do MCP de documentação.
# Webhooks
A plataforma faz um `POST` na sua URL quando algo acontece. Todos os envios passam pelo mesmo motor de entrega: a mensagem é gravada, cada destino recebe uma entrega com retries próprios e cada chamada HTTP fica registrada. Existem três origens:
| Origem | Onde se configura | Custo |
| ------------------------------------------------ | -------------------------------------------------------------- | ----------------------------------- |
| **Eventos da conta** (ex.: `qr.dynamic.scanned`) | **Webhooks** no [dashboard](https://dashboard.respawntech.dev) | Grátis |
| **Callbacks de jobs** (ex.: conversão de PDF) | `webhook_url` em cada requisição | Grátis |
| **Webhook Relay** (seus próprios eventos) | API do [Webhook Relay](/ferramentas/webhook-relay) | Por entrega além da franquia mensal |
## Eventos da conta
Cadastre um endpoint em **Webhooks** no [dashboard](https://dashboard.respawntech.dev) e escolha os eventos. Cada endpoint tem o seu `secret`, mostrado na criação e depois pela opção de revelar o secret.
Esses endpoints são do [Webhook Relay](/ferramentas/webhook-relay#projetos): um endpoint de qualquer projeto que assine um evento da conta o recebe, grátis. A antiga API de webhooks da conta está obsoleta; veja [API legada da conta](/ferramentas/webhook-relay#api-legada).
## Requisição enviada
```http
POST
Content-Type: application/json
User-Agent: RespawnTech-Webhook/2.0
webhook-id: msg_2Vf8tK1pQ9zX
webhook-timestamp: 1790338000
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
X-Webhook-Signature: sha256=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
```
| Header | Conteúdo |
| --------------------- | -------------------------------------------------------------------------------------------------------- |
| `webhook-id` | Identificador da mensagem. É o **mesmo em todos os retries**: use para descartar duplicatas. |
| `webhook-timestamp` | Momento do envio desta tentativa, em segundos Unix. |
| `webhook-signature` | `v1,`, uma assinatura por secret ativo, separadas por espaço (duas durante uma troca de secret). |
| `X-Webhook-Signature` | Assinatura legada `sha256=`, sem timestamp. Mantida para receptores antigos. |
Os headers seguem o padrão [Standard Webhooks](https://www.standardwebhooks.com), então qualquer biblioteca desse padrão valida a requisição. Endpoints do Webhook Relay podem ter headers extras (`custom_headers`).
### Corpo
Eventos da conta e mensagens do Webhook Relay usam o mesmo envelope:
```json
{
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "qr.dynamic.scanned",
"timestamp": 1790338000,
"payload": {
"resource_id": "…",
"slug": "promo-verao",
"ip": "hash_anônimo",
"user_agent": "Mozilla/5.0 …",
"referer": "https://…"
}
}
```
O formato de `payload` varia conforme o `type`. Callbacks de jobs mantêm o corpo próprio (veja [Callback de jobs](#pdf-callback)), e mensagens recebidas de provedores pelo Webhook Relay chegam com o corpo original do provedor, sem alteração.
## Validar a assinatura
O conteúdo assinado é `webhook-id + "." + webhook-timestamp + "." + corpo bruto`, com HMAC-SHA256 e o resultado em base64. Para validar:
1. Leia o **corpo bruto**, antes de qualquer parse de JSON (reformatar o JSON muda a assinatura).
2. Recuse timestamps com mais de de diferença do seu relógio (proteção contra replay).
3. Calcule a assinatura e compare, **em tempo constante**, com cada item de `webhook-signature`. Basta um bater.
Com uma biblioteca Standard Webhooks, use o `standard_webhooks_secret` (formato `whsec_…`). Sem biblioteca, a chave do HMAC são os bytes do `secret` bruto.
```js
// npm i standardwebhooks
import express from 'express';
import { Webhook } from 'standardwebhooks';
const wh = new Webhook(process.env.WEBHOOK_SECRET); // whsec_…
const app = express();
app.post('/hook', express.raw({ type: '*/*' }), (req, res) => {
let event;
try {
event = wh.verify(req.body, req.headers); // lança erro se inválido
} catch {
return res.sendStatus(401);
}
// processe `event` de forma assíncrona e responda rápido
res.sendStatus(204);
});
```
```python
# pip install standardwebhooks
import os
from flask import Flask, request
from standardwebhooks.webhooks import Webhook
wh = Webhook(os.environ["WEBHOOK_SECRET"]) # whsec_…
app = Flask(__name__)
@app.post("/hook")
def hook():
try:
event = wh.verify(request.get_data(), dict(request.headers))
except Exception:
return "", 401
# processe `event` de forma assíncrona e responda rápido
return "", 204
```
```go
// Sem biblioteca: secret é o valor bruto (não o whsec_).
func verify(secret string, r *http.Request, body []byte) bool {
id, ts := r.Header.Get("webhook-id"), r.Header.Get("webhook-timestamp")
t, err := strconv.ParseInt(ts, 10, 64)
if err != nil || math.Abs(time.Since(time.Unix(t, 0)).Seconds()) > 300 {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(id + "." + ts + "."))
mac.Write(body)
want := "v1," + base64.StdEncoding.EncodeToString(mac.Sum(nil))
for _, sig := range strings.Fields(r.Header.Get("webhook-signature")) {
if hmac.Equal([]byte(sig), []byte(want)) {
return true
}
}
return false
}
```
Quem já valida `X-Webhook-Signature` continua funcionando: é `sha256=` + o HMAC-SHA256 em hexadecimal do corpo bruto, com o `secret` bruto como chave. Esse header não tem timestamp; prefira `webhook-signature` em integrações novas.
## Secrets e rotação
| Destino | Onde obter o secret |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Endpoint de eventos da conta | Na criação, ou pela opção de revelar o secret em **Webhooks** no dashboard. |
| Endpoint do Webhook Relay | Na criação, ou em [Revelar secret](/ferramentas/webhook-relay#webhooks-endpoints-secret). |
| Callback de jobs (`webhook_url`) | Secret de assinatura da conta, único para todos os callbacks: `GET /v1/account/webhooks/signing-secret` (rota da conta, com a sessão do dashboard). |
Todo secret aparece em duas formas: `secret` (bruto) e `standard_webhooks_secret` (`whsec_` + base64 dos mesmos bytes). As duas validam as mesmas assinaturas.
Ao [trocar o secret](/ferramentas/webhook-relay#webhooks-endpoints-rotate_secret) de um endpoint, o antigo continua assinando por : nesse período `webhook-signature` traz as duas assinaturas, e você atualiza o receptor sem perder eventos.
## Entrega e retries
Só uma resposta **2xx** conta como sucesso. Redirecionamentos (3xx) não são seguidos, e 3xx, 4xx, 5xx, timeouts e erros de rede são tentados de novo neste calendário:
| Tentativa | Espera antes dela |
| --------- | ----------------- |
| 1 | — |
| 2 | 5 segundos |
| 3 | 5 minutos |
| 4 | 30 minutos |
| 5 | 2 horas |
| 6 | 5 horas |
| 7 e 8 | 10 horas |
São cerca de 27 horas no total, com variação de ±10%. Casos especiais:
* **`429` e `503`** com `Retry-After`: a próxima tentativa respeita o header quando ele pede mais tempo (até 10 horas).
* **`410 Gone`**: a entrega para e o endpoint é desativado.
* Depois da última tentativa, a entrega fica `exhausted`. No Webhook Relay, dá para [reenviar a mensagem](/ferramentas/webhook-relay#webhooks-messages-retry) ou [recuperar as falhas de um endpoint](/ferramentas/webhook-relay#webhooks-endpoints-recover).
Boas práticas no receptor:
* Responda **2xx** o mais rápido possível e processe o evento de forma assíncrona.
* **Descarte duplicatas pelo `webhook-id`**: uma entrega pode chegar mais de uma vez (ex.: seu servidor processou, mas a resposta se perdeu).
* Não dependa da ordem de chegada; use o `timestamp` do corpo quando a ordem importar.
### Desativação automática
Um endpoint que falha sem parar por é desativado automaticamente (`is_active: false`, com `disabled_reason`), assim como um que responde `410`. Depois de corrigir o receptor, reative o endpoint (no dashboard ou com `is_active: true` no Webhook Relay); isso zera a sequência de falhas.
O histórico de mensagens, entregas e tentativas fica guardado por .
## Callback de jobs
Em `POST /convert` ou `POST /batch` do [PDF Convert](/ferramentas/pdf-convert), envie `webhook_url` no body. Quando o job termina, a API faz um `POST` nessa URL:
```json
{
"job_id": "job_abc123",
"success": true,
"result_url": "https://…",
"metadata": { "conversion_id": "…" }
}
```
Em falha, `success` é `false` e o campo `error` traz a mensagem. O callback tem os mesmos headers, assinatura e retries dos outros webhooks, assinado com o **secret de assinatura da conta** (veja [Secrets e rotação](#secrets)). A `webhook_url` precisa ser uma URL pública `http` ou `https`; caso contrário a requisição é recusada com `webhook.invalid_url`.
## Seus próprios eventos
Para enviar webhooks da **sua** aplicação para os seus clientes, com a mesma assinatura, retries e log, ou para receber webhooks do Stripe, GitHub e outros e redistribuí-los, use o [Webhook Relay](/ferramentas/webhook-relay).
# Áudio
## Visão geral
Toda operação de áudio é **assíncrona** e cobrada na hora:
1. `POST` na operação → **202 Accepted** com `job_id`, `status: pending` e `poll_after_seconds`.
2. Um worker processa em background.
3. Consulte `GET /jobs/{id}` até o `status` ser `completed` (ou `failed`, e a cobrança é devolvida). Pronto, a resposta traz o resultado e um **link assinado** que expira.
Os mesmos casos de uso estão no [MCP](/agentes-de-ia) como as tools `audio_*` (toolset `audio`); a REST e o MCP compartilham formatos, preços e limites.
Os créditos são debitados quando o job é aceito. Falha do motor, etapa que não foi necessária e preço calculado a mais são **devolvidos** automaticamente; nada é cobrado se o pedido for recusado antes (`audio.too_long`, `audio.price_above_max`…).
## Entrada do áudio
As operações que recebem um áudio pedem **exatamente um** destes campos:
| Campo | O que é |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `audio_url` | URL pública `http(s)` do arquivo. Endereços de rede interna ou privada são recusados (`audio.input_blocked`) e o download tem limite de |
| `audio_base64` | O áudio inline em base64, até decodificado |
| `file_id` | Id de um arquivo de áudio já enviado para a conta |
Formatos aceitos: mp3, wav, ogg, m4a, flac e webm. O limite é de de áudio e por arquivo por chamada. Em `summarize` e `translate` você pode mandar, no lugar do áudio, o `job_id` de uma transcrição já pronta: a transcrição é reaproveitada e só a operação nova é cobrada.
Todas as rotas de operação aceitam `max_credits` (em unidades; 10.000 unidades = 1 crédito): se o preço passar disso, o pedido é recusado com `audio.price_above_max` e nada é cobrado. O header `Idempotency-Key` torna a repetição segura: a mesma chave devolve o primeiro job em vez de cobrar de novo.
## Custos
O "minuto iniciado" arredonda para cima. Um áudio cuja duração não dá para ler do cabeçalho é cobrado pelo teto de e a diferença volta no fim do job. Consultas aos jobs seguem a [franquia diária](/cobranca-e-limites#franquia-diaria). O preço exato de uma chamada sai do [dry-run](#dry-run).
### Dry-run
Com `?dry_run=true` na query (nas rotas de áudio é a query, não o header `X-Dry-Run`), as operações pagas respondem `200` com o preço e não cobram nem criam job:
```json
{
"success": true,
"data": {
"service_code": "audio_transcribe_minute",
"units": 3,
"rate": 15000,
"total_units": 45000,
"duration_ms": 125000,
"duration_known": true,
"parts": [{ "service_code": "audio_transcribe_minute", "units": 3, "rate": 15000, "total": 45000 }]
}
}
```
`rate` e `total_units` estão em unidades (10.000 unidades = 1 crédito); `parts` detalha cada serviço cobrado. Em `speak`, o orçamento traz `chars`, `voice`, `language` e `format`.
## Operações
### Transcrever áudio
`format`: `text` (padrão), `srt` ou `vtt`. `language` é opcional (código ISO 639-1; sem ele o idioma é detectado). Com `diarize: true` (ou `speakers`, o número conhecido de falantes) cada trecho diz quem falou (`Speaker 1`, `Speaker 2`…), por um adicional por minuto.
### Gerar fala a partir de texto
`text` aceita até caracteres (acima disso o pedido é recusado antes de cobrar). `voice` é um id do [catálogo](#vozes-do-catalogo) ou uma voz clonada da conta (`vc_…`, [só no MCP](#vozes-clonadas)); sem `voice`, vale a voz padrão do `language` (`pt-BR`, `en-US`, `en-GB`, `es-ES`, `fr-FR` ou `it-IT`). `format`: `mp3` (padrão) ou `wav`; `speed`: 0,5 a 2.
### Listar as vozes
Catálogo fechado de vozes do `speak` e do `revoice`. `language` filtra por idioma. Não é cobrado.
### Gerar efeito sonoro ou música
`kind`: `effect` (1 a , padrão 5 s) ou `music` (instrumental, 1 a , padrão 30 s). O áudio sai com exatamente `duration_seconds`. Não há fala nem canto, e descrições que imitam artistas, vozes ou obras existentes são recusadas antes de cobrar (`audio.input_blocked`). `seed` repete um som: o mesmo pedido com a `seed` que um job informou gera o mesmo áudio. A geração leva alguns minutos.
### Analisar áudio
Sem IA de transcrição: devolve em `analysis` a duração, o formato e codec, bitrate, taxa de amostragem, canais, loudness integrado (LUFS), pico real, faixa de loudness, os trechos de silêncio (`start_ms`/`end_ms`) e o idioma falado. Preço fixo por chamada.
### Converter ou normalizar áudio
`format` (obrigatório): `mp3`, `wav`, `ogg`, `m4a` ou `flac`. `bitrate_kbps` (32 a 320) vale só para formatos com perda. `normalize: true` nivela o loudness em -14 LUFS (padrão de streaming, -1,5 dBTP). As tags de metadados são removidas.
### Resumir áudio
Mande o `job_id` de uma transcrição pronta (o áudio não é processado de novo) ou um áudio novo (transcrito primeiro e cobrado junto). O resultado vem em `summary`: o resumo e os tópicos em ordem (`title`, `start_ms`, `points`), mais um Markdown para baixar. O texto da transcrição (nunca o áudio) é enviado ao modelo de linguagem da plataforma.
### Traduzir áudio
`target_language` é obrigatório. A tradução sai em texto, SRT ou VTT com os tempos originais (e os falantes, se a transcrição tinha `diarize`); `source_language` informa de qual idioma ela veio. Não gera áudio dublado: para isso, use o [revoice](#revoice).
### Falar a gravação de novo com outra voz
Num só job: transcreve a gravação, traduz o texto quando a voz fala outro idioma e sintetiza o resultado. Informe `voice_id` (do catálogo) ou `language` (usa a voz padrão dele). O áudio novo não mantém o tempo nem o ritmo do original (sem sincronia labial), e só vozes do catálogo ou clonadas da própria conta valem: nada de imitar uma pessoa real. O resultado traz o texto falado (`transcript` e `text_download_url`) e o que cada etapa custou em `revoice.stages`.
### Listar jobs
Os jobs mais recentes primeiro, com `limit` (padrão 20, até ) e `offset`. A lista traz só o status; o resultado e o link ficam em `GET /jobs/{id}`. Jobs e arquivos são apagados depois da retenção ().
### Status, resultado e download
Não há rota de download separada: o resultado e o arquivo vêm deste endpoint. Com `status: completed`, a resposta traz o conteúdo pelo tipo de job — `transcript` (também inline até 32 KB; `truncated: true` avisa o corte) para transcrição e tradução, `summary`, `analysis`, `conversion`, `generation` ou `revoice` — e `download_url` com `download_expires_at`. O link é assinado e expira: consulte de novo para receber um link novo. Em `failed`, o `error_code` explica o motivo e a cobrança é devolvida. A leitura é medida na [franquia diária](/cobranca-e-limites#franquia-diaria).
`status`: `pending`, `processing`, `completed` ou `failed`.
## Vozes clonadas e consentimento (só MCP)
Enquanto não existir uma rota REST, a clonagem de voz está **somente nas tools MCP** `voice_create`, `voice_list` e `voice_delete` (scope `tools:audio:speak`). A voz clonada serve só à conta que a criou, em `audio_speak` (`voice: "vc_…"`, preço de voz clonada) e em `audio_revoice` (`voice_id`).
* **`voice_create`** clona a voz **do titular da conta** a partir de uma amostra de . O consentimento é obrigatório: chame primeiro com `dry_run` para ler `consent_terms`, `consent_version` e `consent_phrase`; grave a amostra **com você sozinho lendo a frase em voz alta**; então chame com `consent.accepted: true` e a `consent_version`. Amostras sem a frase, com mais de uma voz ou fora da duração são recusadas e o valor é devolvido (`audio.voice_sample_rejected`). Sem o aceite, a resposta é `audio.consent_required`. É assíncrono: acompanhe com `voice_list` até a voz ficar `completed`.
* **`voice_list`** lista as vozes da conta (`voice_id`, nome, idioma, `status`, validade). Grátis.
* **`voice_delete`** apaga a voz de verdade: a amostra criptografada e o registro de consentimento são destruídos e a voz para de funcionar na hora (jobs na fila nela falham e são devolvidos). Grátis.
### Uso responsável
* **Só a sua voz, com o seu consentimento.** Clonar a voz de outra pessoa, mesmo com a gravação em mãos, não é permitido; a verificação da frase falada e de um único falante existe para impedir isso.
* **Nada de imitação.** Nem a fala, nem o `revoice`, nem a geração de sons aceitam imitar artistas, vozes ou obras existentes.
* **Limites:** até vozes clonadas por conta, criações por dia e usos por hora (`audio.voice_limit`, `audio.voice_rate_limited`).
* **Dados guardados:** a amostra fica criptografada por até ou até o `voice_delete`; cada criação, recusa, uso e exclusão é auditado, sem conteúdo de voz. O áudio de entrada e de saída dos demais jobs expira em .
* **Aviso ao ouvinte:** se você publica áudio sintético, avise que a voz é gerada, principalmente quando for uma voz clonada.
## Scopes
| Scope | Operações |
| ------------------------ | --------------------------------------------------------------------------------- |
| `tools:audio:transcribe` | `transcribe`, `summarize`, `translate` |
| `tools:audio:speak` | `speak`, `generate`, `revoice` (no MCP, também `voice_create` e `voice_delete`) |
| `tools:audio:process` | `analyze`, `convert` |
| `tools:audio:read` | `voices`, `jobs`, `jobs/{id}` (os três scopes acima também leem os próprios jobs) |
`tools:manage` cobre tudo. A lista completa fica em [Autenticação](/autenticacao).
## Erros
`audio.*` na resposta ou, em jobs que falham depois de aceitos, no `error_code` do job.
# Base de conhecimento
## Visão geral
Uma **base** (collection) guarda documentos de texto ou Markdown. Cada documento é dividido em **seções** (pelos títulos) e **trechos** (pedaços de \~96 tokens), que são indexados de duas formas: por significado (embeddings) e por palavras-chave. A busca combina as duas e devolve os trechos mais relevantes, prontos para você passar a um modelo de linguagem (RAG) ou mostrar ao usuário.
**Crie uma base** com o idioma dos documentos (melhora a busca por palavras-chave).
**Alimente a base.** Envie texto ou Markdown, importe arquivos (PDF, Office, imagens) ou conecte uma fonte que se sincroniza sozinha (site, sitemap, repositório do GitHub, pasta do Google Drive). A indexação é assíncrona: acompanhe o `status` até `ready`. Reenviar com o mesmo `external_id` substitui a versão anterior sem tirar a base do ar.
**Busque** com a pergunta do usuário e use os trechos devolvidos como contexto da resposta.
**Responda com citações** (opcional): `answer` devolve um texto em que cada afirmação aponta para o trecho de origem, ou diz que não sabe. Para o seu site, embuta o [widget](#widget).
**Registre feedback** (opcional): trechos confirmados sobem nas próximas buscas parecidas e os rejeitados somem.
## Custos
Buscas e rerank têm uma franquia grátis por dia. Depois dela, a chamada que abre um bloco debita o bloco inteiro de uma vez (10 chamadas no primeiro, depois 20, 50 e 100 conforme o uso pago do dia), e as próximas do mesmo bloco não cobram de novo. Os contadores zeram à meia-noite UTC, e as chamadas não usadas de um bloco expiram sem estorno. A ferramenta MCP `rag_get_usage` e o `dry_run` das buscas mostram quanto a próxima chamada cobra. Consultar bases, documentos e feedback é grátis e não entra na [franquia diária de leituras](/cobranca-e-limites#franquia-diaria).
A ingestão é cobrada **quando termina**, pelo número real de trechos; o `estimate` da resposta é uma aproximação. Conteúdo idêntico ao já indexado não é cobrado de novo. Use `X-Dry-Run: true` no envio de documento para ver a estimativa sem enviar nada.
## Bases
### Criar base
* `name` (obrigatório, até 120 caracteres) e `description` (até 1.000).
* `language`: `simple` (padrão), `portuguese`, `english`, `spanish` ou `french`. Define o dicionário da busca por palavras-chave.
* `default_mode`: `fast` (padrão) ou `accurate` (rerank extra, mais preciso e um pouco mais caro).
* Ajustes avançados: `chunk_size_tokens` (16–480, padrão 96), `chunk_overlap_tokens` (padrão 16), `section_max_tokens` (padrão 768), `max_per_document` (0–20, padrão 2), `dense_weight` e `lexical_weight` (0–10, padrão 1; não podem ser os dois 0) e `feedback_enabled` (padrão `true`).
### Listar bases
Devolve também o uso da conta em relação aos limites (`usage`).
### Consultar base
### Filtros disponíveis
Lista as chaves de metadados dos documentos da base e os valores mais comuns de cada uma, para você saber o que pode usar em `filters` nas buscas. `documents` é quantos documentos prontos com metadados foram analisados e `keys` é o total de chaves filtráveis. Se `sampled` for `true`, a base tem mais de 5000 documentos e a contagem vem só dos 5000 mais recentes. Em `facets`, cada item traz a `key`, os `documents` que a têm, `distinct` (total de valores distintos) e `values[]` (os valores mais comuns, cada um com `value` e `documents`). `array: true` indica que a chave guarda uma lista; filtre com `{"chave": ["valor"]}`.
### Alterar base
Envie só os campos que quer mudar. O modelo de embeddings não pode ser trocado depois da criação. Mudanças no tamanho dos trechos só valem para documentos enviados depois; para aplicar à base inteira, use a reindexação.
### Reindexar base
Reprocessa todos os documentos com as configurações atuais da base. Cada trecho é cobrado como numa ingestão nova.
### Painel de saúde da base
Resumo para manter a base em ordem, sem custo: contagem de documentos por `status`, quantos estão desatualizados (`stale`), nunca foram consultados ou não têm metadados; uma matriz de documentos por metadado (`rows` e `cols` são chaves de metadado, até 20 valores por eixo); a atividade dos últimos `days` dias (1 a 180, padrão 30); e `attention`, com até 5 exemplos de documentos com falha, desatualizados, duplicados ou contraditórios e com feedback negativo. A resposta fica em cache por 60 s.
### Índice dos documentos
Lista compacta dos documentos `ready`, sem conteúdo, para o agente escolher o que ler. `filters` (JSON de metadados), `fields` (até 20 chaves de metadado a copiar), `order` (`updated_at_desc`, padrão, ou `title`) e `max_bytes` (padrão 4096, máximo 32768). `omitted` conta os documentos cortados pelo limite.
### Achar duplicados e contradições
Compara os 500 documentos mais recentes (opcionalmente filtrados por `filters`) com os embeddings que já existem; não chama modelo e não escreve nada. `duplicate` aparece com similaridade a partir de 0,75; `possible_contradiction`, entre 0,4 e 0,75 quando os dois têm o mesmo metadado `tipo`. Cada achado diz qual documento manter (`keep`, o mais novo) e o que fazer (`action`). `limit` vai de 1 a 100 (padrão 20) e `include_content: true` devolve o início de cada documento.
### Trocar o modelo de embeddings
Troca entre `intfloat/multilingual-e5-small` (padrão) e `BAAI/bge-m3` (Alta precisão) reindexando a base **em segundo plano**; você não precisa chamar a reindexação. A troca custa 50% do preço normal de ingestão, e voltar ao modelo anterior é grátis enquanto ela roda e por 24 h depois, uma vez. Use `X-Dry-Run: true` para ver a estimativa. Durante a troca a busca continua funcionando (a consulta é embutida com os dois modelos) e documentos novos são recusados com `rag.model_switch_in_progress`. Acompanhe pelo campo `model_switch` da base (`running`, `paused` ou `done`); se o saldo acabar, ela pausa com `paused_reason: insufficient_credits` e retoma quando você recarregar.
### Marcar documentos como desatualizados
Para bases ligadas a código ou a arquivos: informe os `paths` que mudaram (arquivo exato ou prefixo `dir/`, até 1000) e os documentos que declararam esses `paths` ao serem enviados ficam com `stale: true`. Nada é reindexado nem cobrado; reenviar o documento limpa a marca. Use `exclude_stale` na busca para ignorá-los. `commit` (até 64 caracteres) evita marcar o que já foi conferido naquele commit.
### Excluir base
A exclusão é assíncrona e apaga documentos, trechos e feedback.
## Documentos
### Adicionar ou substituir documento
Segurança…",
"source_uri": "https://ajuda.example.com/artigos/42",
"metadata": { "categoria": "conta" }
}` }}
>
* `content` (obrigatório): texto puro ou Markdown em UTF-8, até . Para PDF, Office e imagens, use [Importar arquivo](#rag-documents-file).
* `external_id` (opcional, até 200 bytes): o seu identificador. Reenviar com o mesmo valor **substitui** o documento; a versão anterior continua respondendo às buscas (`processing: true`) até a nova ficar pronta.
* `paths` (até 50) e `commit`: os arquivos de onde o documento vem, para [marcá-lo como desatualizado](#rag-collections-changes) quando mudarem.
* Segredos reconhecidos no texto (chaves de API, tokens) são trocados por `[REDACTED:tipo]` antes de indexar; `redactions` conta quantos.
* `metadata`: objeto com até 50 chaves (4 KB, até 3 níveis), usado nos filtros da busca. Não é criptografado; não coloque dados sensíveis.
* Acompanhe com [Consultar documento](#rag-documents-get) até `status: ready` (ou `failed`, com `error_code`). Antes de aceitar, a API confere se o saldo cobre a estimativa.
### Importar arquivo (PDF, Office, imagem)
Envio `multipart/form-data` com `file` (obrigatório, até 50 MB) e, opcionalmente, `title`, `external_id`, `source_uri`, `metadata` (objeto JSON em texto) e `ocr` (padrão `true`; desligue para PDFs que já têm texto). Formatos: `.pdf`, `.docx`, `.pptx`, `.xlsx`, `.html`, `.md`, `.csv`, `.asciidoc` e imagens (`.png`, `.jpg`, `.tiff`, `.bmp`, `.webp`). O arquivo é convertido em Markdown (tabelas preservadas, imagens só pelo texto do OCR) e depois indexado como qualquer documento, com o mesmo limite de para o texto resultante. A resposta é `202` com o `id` da importação; consulte-a até `completed` (traz o `document_id`) ou `failed`. Sem conversão disponível, a resposta é `503` (`rag.service_unavailable`).
### Consultar importação de arquivo
### Listar documentos
Filtro opcional por `status` (`pending`, `processing`, `ready`, `failed`, `deleting`). Paginação por cursor: passe o `next_cursor` recebido em `after`. `limit` padrão 20, máximo 100.
### Consultar documento
### Alterar título ou metadados
Não reprocessa o conteúdo e não cobra nada.
### Ver seções indexadas
Mostra como o documento foi dividido, útil para ajustar títulos e tamanho dos trechos.
### Reprocessar documento com falha
Só para documentos com `status: failed`; os demais respondem `rag.document_not_failed`.
### Excluir documento
## Fontes sincronizadas
Uma **fonte** importa páginas de fora e as mantém em dia: a cada sincronização, páginas novas ou alteradas são reindexadas, as inalteradas (mesmo conteúdo) não são cobradas de novo, e as que sumiram da origem são removidas da base. Cada página vira um documento comum (`external_id` `src::…`, com `source_id` e `source_url` nos metadados), cobrado por trecho como numa ingestão manual; se faltar saldo ou cota, a sincronização falha e mostra o motivo.
| `kind` | `start_url` | O que importa |
| --------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `site` | página inicial | Rastreia links do mesmo host até `max_depth`, respeitando o `robots.txt`; HTML vira Markdown e arquivos PDF/Office linkados são convertidos. |
| `sitemap` | URL do sitemap (ou índice) | Importa as páginas listadas, até `max_pages`. `max_depth` não se aplica. |
| `github` | `https://github.com/dono/repo` | Repositório **público**; `config.ref` (padrão `HEAD`) e `config.paths` (prefixos) filtram. Lê `.md`, `.mdx`, `.txt`, `.rst` e converte os formatos de arquivo suportados. |
| `gdrive` | URL de uma pasta `/folders/` | Pasta compartilhada como "qualquer pessoa com o link"; Docs viram Markdown. Só existe se o servidor tiver a integração ligada. |
### Listar fontes sincronizadas
### Criar fonte sincronizada
* `name` (até 200) e `start_url` (http ou https) são obrigatórios; `kind` padrão `site`.
* `max_pages`: 1 a 1000 (padrão 100). `max_depth`: 1 a 10 (padrão 3).
* `frequency_hours`: `0` (só manual), `6`, `24` (padrão) ou `168`.
* A primeira sincronização começa na hora. Páginas e arquivos de até 20 MB cada; um arquivo que não converte é pulado sem falhar a sincronização.
### Alterar fonte
Todos os campos são opcionais. Com `enabled: false` ou `frequency_hours: 0`, `next_sync_at` fica nulo.
### Sincronizar agora
Responde `409` se a fonte já estiver sincronizando. `pages_total`, `pages_new`, `pages_changed` e `pages_removed` resumem a execução.
### Histórico de sincronizações
### Excluir fonte
## Busca
### Modos de busca e reranker
* **`fast`** (padrão): combina a busca por significado e a por palavras-chave. O `score` é uma fusão de posições (valores pequenos, perto de 0,03), bom para ordenar, não para comparar com um limiar.
* **`accurate`**: depois da fusão, um **reranker** relê os 20 melhores candidatos junto com a pergunta e devolve um `score` calibrado de 0 a 1; só nele `min_score` tem efeito. Cobra o adicional de rerank. Se o rerank estiver indisponível, a busca cai para `fast` sem o adicional (`mode_used`).
* **Reranker**: `rerank_provider` da base (e `provider` no rerank avulso e no `decide`) escolhe entre `bge` (padrão, cross-encoder) e `laya`, o juiz de relevância da Respawn, que pergunta se o trecho responde à consulta e devolve a probabilidade. O Laya costuma separar melhor trechos que só se parecem com a pergunta, mas é mais lento e, se estiver fora do ar, o `accurate` cai para `fast` e o rerank avulso responde `503` (`rag.service_unavailable`). O preço é o mesmo do `bge`.
* Se o serviço de embeddings falhar, a busca ainda responde só por palavras-chave (`mode_used: lexical`), cobrada como cache.
### Buscar na base
* `query` (obrigatório, até caracteres).
* `top_k`: quantos trechos devolver (padrão 5, máximo ).
* `mode`: `fast` ou `accurate`; sem ele, vale o padrão da base. Se o rerank do modo `accurate` estiver indisponível, a busca cai para `fast` sem cobrar o adicional; `mode_used` mostra o que foi usado.
* `filters`: só trechos de documentos cujos metadados contêm estes valores.
* `min_score` (só no `accurate`) e `max_per_document` (0–20) refinam o resultado.
* `updated_after` e `updated_before` (RFC 3339) filtram pela data de atualização do documento, e `exclude_stale: true` ignora os marcados como desatualizados. Cada resultado traz `updated_at`, `stale`, `paths`, `commit`, `source_uri` e `also_in` (outros documentos com o mesmo texto, colapsados num só resultado).
* `highlights` marca, em posições de caractere Unicode, os termos encontrados em `content`. Consultas repetidas em até vêm do cache (`cached: true`) e custam menos.
Os trechos devolvidos são conteúdo dos seus documentos. Ao passá-los a um modelo de linguagem, trate-os como dados, não como instruções.
## Feedback de relevância
### Registrar feedback de relevância
* `label`: `positive` (o trecho responde à pergunta) ou `negative`.
* `end_user_ref` (opcional): identifica quem avaliou, quando o voto vem do seu usuário final. É guardado só como hash.
* Até votos por minuto. Perguntas parecidas são agrupadas, e o voto passa a valer para todo o grupo.
### Listar feedback
### Apagar um grupo de feedback
## Rerank avulso
### Reordenar textos (rerank)
Ordena **a sua** lista de textos pela relevância à pergunta, sem base envolvida. Até textos por chamada (cada um é cortado em 2.000 caracteres). `index` aponta para a posição no array enviado.
## Resposta com citações
### Responder com citações
Faz uma busca `accurate` e responde **só** com trechos de `score` igual ou acima de `min_score` (0 a 1, padrão 0,35, um ponto de partida que vale calibrar na sua base).
* Com resposta, `answered: true` e cada `[n]` do texto aponta para uma `citations[]` (`section_id`, `document_id`, `title`, `heading_path`, `source_uri`, `score`, `excerpt`). Nunca há resposta sem citação.
* Sem trecho acima do limiar, `answered: false`, `reason: no_passage_above_threshold`, nenhuma citação, e o webhook `rag.question.unanswered` é disparado. Se o rerank não pôde conferir a relevância, `reason: relevance_unverified` (não afirma nada e não dispara o webhook).
* `generate: false` (padrão) monta a resposta citando os 3 melhores trechos. Com `generate: true`, o provedor de IA padrão da plataforma escreve o texto a partir dos trechos; sem marcador de citação válido ou sem provedor, volta ao modo extrativo (`generated: false`). Com texto gerado, os trechos da base seguem para o provedor de IA da plataforma.
* Com `Accept: text/event-stream`, a resposta vem em SSE: `citations`, vários `delta` (pedaços do texto) e `done` (o resultado completo). O texto é gerado inteiro antes, o SSE só o fatia.
* Cobrança de uma busca `accurate`; a geração não tem cobrança à parte. Use o `query_id` no [feedback](#rag-feedback-add) para alimentar o CTR do analytics.
## Widget de chat embutível
O widget é um botão de chat em JavaScript puro (sem CDN, em shadow DOM, só escreve texto) que responde com citações usando a sua base. Ele usa uma **chave pública** (`rpk_…`) que só consegue perguntar numa base: não lê outras, não escreve nada.
```html
```
Atributos: `data-key` (obrigatório), `data-title`, `data-placeholder` e `data-dont-know` (texto quando não há resposta). As perguntas feitas pelo widget são cobradas do dono da base, como o `answer`.
### Criar chave pública (widget)
* `allowed_origins` (obrigatório, 1 a 20): `https://site.com` ou `https://*.site.com`, sem caminho. O servidor recusa pedidos cujo `Origin` não esteja na lista.
* `rate_per_minute`: 1 a 600 (padrão 30), por chave. `generate` liga o texto gerado por IA nas respostas dessa chave.
* Até 20 chaves por base. A `key` aparece **uma única vez**; guardamos só o hash.
Só o navegador respeita o `Origin`; um cliente fora dele pode forjá-lo. O que limita o abuso é o limite por minuto da chave, e o custo recai sobre você. Revogue a chave se vir uso estranho.
### Listar chaves públicas
### Revogar chave pública
### Responder pelo widget (rota pública)
Sem token de API: envie a chave no header `X-Respawn-Public-Key` e o navegador manda o `Origin`. A base vem da chave, e `top_k`, `min_score` e `generate` não são aceitos no pedido. Responde como o `answer` (JSON ou SSE), com CORS liberado para a origem permitida. Erros: `401` (chave ausente, desconhecida ou revogada), `403` (origem não permitida) e `429` (limite por minuto).
### Script do widget
## Analytics de perguntas
### Analytics das perguntas
Mostra o que os usuários perguntam e o que a base não sabe responder, a partir das chamadas de `answer` e do widget. `days` vai de 1 a 90 (padrão 30).
* `answer_rate`: respondidas ÷ total de perguntas.
* `feedback_ctr`: respondidas que receberam voto (pelo `query_id`) ÷ respondidas.
* `positive_rate`: votos positivos ÷ votos.
* `top_questions` e `unanswered_questions`: até 20 perguntas agrupadas cada, para você descobrir o que escrever na base.
As perguntas ficam cifradas com a chave da base. O widget não tem voto próprio: o CTR só conta votos enviados pela API.
## Roteamento
### Decidir entre opções (roteamento)
Classifica um texto entre 2 e 50 opções sem usar um LLM, pontuando cada `label: description` com o reranker (`provider`: `bge` ou `laya`). Serve para rotear pedidos. Se a melhor opção não passar de `threshold` (padrão 0,5) ou não ficar `min_margin` (padrão 0,1) à frente da segunda, `decided` é `false`: trate como "não sei". O `ranking` completo vem sempre. Preço e franquia do rerank avulso.
## Preços e limites da conta
### Consultar preços
### Consultar limites
Valores vigentes da sua conta (já com o bônus do plano), úteis para validar um envio antes de fazê-lo.
## Eventos (webhooks)
Cadastre um endpoint no dashboard para receber:
* `rag.document.ready` e `rag.document.failed`: `collection_id`, `document_id`, `external_id` e `version`; o de falha traz também `error_code`. Valem para documentos enviados, importados de arquivo e vindos de fontes.
* `rag.question.unanswered`: `collection_id`, `question`, `query_id` e `source` (`api` ou `widget`), quando nenhum trecho passou do `min_score`. Bom para alimentar uma fila de "o que escrever".
O envio é de melhor esforço, como nos demais eventos. Veja a [lista de eventos](/webhooks).
## Usar pelo MCP
As mesmas operações estão disponíveis como ferramentas MCP do grupo `rag` ([lista completa](/agentes-de-ia)): `rag_answer`, `rag_decide`, `rag_index`, `rag_lint`, `rag_mark_changed` e `rag_retry_document` são as novas. As de leitura (`rag_index`, `rag_lint`) são gratuitas; use `rag_access=query` na conexão para esconder as que alteram a base.
## Erros
Os códigos sem status HTTP aparecem no campo `error_code` de um documento com `status: failed`.
## Limites
| Limite | Valor |
| ------------------------------------------ | -------------------------------------------- |
| Tamanho de um documento | |
| Bases por conta | |
| Trechos indexados por conta | |
| Documentos em processamento ao mesmo tempo | |
| Tamanho da pergunta | caracteres |
| Trechos por busca (`top_k`) | |
| Textos por rerank avulso | |
| Votos de feedback por minuto | |
| Arquivo importado | 50 MB |
| Páginas por fonte | 1.000 (padrão 100) |
| Chaves públicas por base | 20 |
As rotas da base de conhecimento ignoram o header `X-Idempotency-Key`: elas já são idempotentes (conteúdo repetido não é reindexado nem cobrado). Uma chave restrita a bases específicas só enxerga essas bases e não pode criar novas (`forbidden`).
# Extrator de erro de CI
## Visão geral
Envie o log de um job de CI e receba apenas as linhas do erro. Serve para agentes e bots que precisam entender por que um build falhou sem carregar milhares de linhas no contexto. Também está disponível no [MCP](/agentes-de-ia) como `ci_error_extract`.
## Custos
Cada extração é uma leitura medida: entra na [franquia diária](/cobranca-e-limites#franquia-diaria) e, depois dela, debita o valor indicado no endpoint. A cobrança vale só para chaves de API.
## Endpoints
### Extrair o erro de um log
## Erros
# Memória de aprendizados
## Como funciona
Um aprendizado é um texto curto com um `kind`, ligado a um `project`:
| `kind` | Para guardar |
| ------------ | -------------------------------------------- |
| `pitfall` | Armadilhas e erros que não devem se repetir. |
| `decision` | Decisões tomadas e o motivo. |
| `convention` | Convenções do projeto. |
| `howto` | Como fazer algo (rodar, publicar, depurar). |
| `other` | O que não cabe nos outros. |
Os aprendizados ficam numa [base de conhecimento](/ferramentas/base-de-conhecimento) chamada `agent-learnings`, **criada sozinha** na primeira gravação. O `recall` faz uma busca semântica pela tarefa e devolve só os itens do `project` pedido, com a `score` de cada um.
Também está disponível no [MCP](/agentes-de-ia) como `learning_save`, `learning_recall` e `learning_forget` (toolset `learnings`).
## Duplicados e ingestão assíncrona
* **Deduplicação:** o `save` compara por hash e por similaridade semântica. Se já existe um aprendizado igual ou parecido, nada novo é gravado e a resposta traz `duplicate_of` com o id do existente.
* **Ingestão assíncrona:** o texto é indexado em segundo plano. Um `recall` logo depois de um `save` pode ainda não trazer o item; tente de novo em alguns segundos.
## Custos
A cobrança é a da [base de conhecimento](/ferramentas/base-de-conhecimento): o `save` como a ingestão (por trecho), o `recall` como a busca (franquia diária + blocos) e o armazenamento por dia. Esquecer é grátis. A memória segue as mesmas regras de liberação da base de conhecimento (`rag.disabled` e demais erros `rag.*`).
## Endpoints
### Salvar aprendizado
`project`, `kind` e `text` são obrigatórios; `source` é opcional e diz de onde veio. A resposta traz o `id` do aprendizado ou, se for repetido, `duplicate_of`.
### Recuperar aprendizados
`project` e `task` são obrigatórios; `top_k` limita quantos itens voltam. Cada item traz `id`, `kind`, `text`, `source` e `score`. Lembre que a ingestão é assíncrona: um item recém-salvo pode ainda não aparecer.
### Esquecer aprendizado
# Notificações
## Como funciona
Uma notificação tem `title`, `message` e `priority` (`normal` ou `high`), e opcionalmente uma `url` para o humano abrir. Ela sai por um **canal**:
* **Canal escolhido:** o `channel` do pedido, pelo nome do canal.
* **Canal padrão:** o canal marcado com `is_default`, quando o pedido não traz `channel`.
* **E-mail da conta:** sem canal nenhum configurado, a notificação vai para o e-mail da conta.
Também está disponível no [MCP](/agentes-de-ia) como a tool `notify` (toolset `notify`): o agente avisa que terminou uma tarefa longa ou que precisa de uma decisão.
## Canais
Cada canal tem um `name` único na conta e um `kind`:
| `kind` | Campos | Como entrega |
| --------- | ----------------- | ----------------------------------------------------------------------------------- |
| `email` | nenhum | Envia **só para o e-mail da conta**. Não é possível escolher outro destinatário. |
| `webhook` | `url`, `secret` | Faz um `POST` na URL, assinado como no [Webhook Relay](/ferramentas/webhook-relay). |
| `ntfy` | `server`, `topic` | Publica no tópico do [ntfy](https://ntfy.sh), no servidor informado. |
`is_default` marca o canal usado quando o pedido não indica um, e `enabled: false` desativa o canal sem apagá-lo. O `secret` do webhook nunca volta nas respostas: guarde-o ao criar o canal.
Em servidores públicos do ntfy, quem souber o tópico lê as notificações. Use um nome longo e imprevisível, ou um servidor seu.
## Limites, retry e histórico
* **Rate limit:** há um limite de notificações por período; acima dele o envio falha com `notify.rate_limited`.
* **Retry do webhook:** uma entrega que falha é tentada de novo algumas vezes antes de virar `notify.delivery_failed`. O receptor deve tratar repetições e conferir a assinatura.
* **Histórico:** `GET /messages` lista os envios (canal, prioridade, status e data), paginado com `page` e `page_size` (até 100). O histórico **não guarda o título nem o corpo da mensagem**.
## Custos
Cada envio pela API é cobrado conforme o valor indicado no endpoint. Canais e histórico são grátis, e a falha na entrega não é cobrada.
## Endpoints
### Enviar notificação
`title` e `message` são obrigatórios; `priority` é `normal` ou `high`; `url` e `channel` são opcionais. Sem `channel`, usa o canal padrão e, sem nenhum canal, o e-mail da conta.
### Histórico de envios
Paginado por `page` e `page_size` (até 100). Não traz o corpo das mensagens.
### Listar canais
### Criar canal
`name` e `kind` são obrigatórios. `url` e `secret` valem para `webhook`; `server` e `topic`, para `ntfy`. Campos que não combinam com o `kind` respondem `notify.invalid_channel`.
### Consultar canal
### Alterar canal
Envie só os campos que mudam. Um canal desativado responde `notify.channel_disabled` quando escolhido num envio.
### Excluir canal
### Testar canal
Envia uma notificação de teste pelo canal, para conferir a configuração antes de depender dele.
## Erros
# PDF Convert
## Visão geral
O PDF Convert é **assíncrono**:
1. `POST /convert` ou `POST /batch` → **202 Accepted** com o `conversion_id` (o `{id}` das demais rotas).
2. Um worker renderiza em background (Chromium headless).
3. Acompanhe pelo [callback `webhook_url`](/webhooks#pdf-callback) ou consultando `GET /{id}`.
4. Baixe com `GET /{id}/download` quando `status` for `completed`.
Os créditos são debitados quando o job é aceito. Se o enfileiramento de um batch falhar depois do débito, o valor é estornado automaticamente.
## Custos
O batch aceita até ** itens**. O retry cobra o custo integral novamente, inclusive os adicionais acima. Salvar templates e enviar fontes não custa créditos. Consultas e downloads via API seguem a [franquia diária](/cobranca-e-limites#franquia-diaria).
## Conversão
### Converter HTML em PDF/PNG/JPG
Fatura