Respawn Tech Dev

Cobrança e limites

Créditos, dry-run, idempotência, rate limit, franquia diária de leituras e timeouts.

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, PDF Convert, Base de conhecimento, Áudio.
  • Cada token pode ter um limite diário de gasto: passar dele responde billing.spend_limit_reached.

Preço exato

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

HeaderEfeito
X-Dry-Run: trueValida e calcula o custo sem persistir nem debitar
X-Preview-Cost: trueAlias de X-Dry-Run
X-Include-Cost: trueInclui 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 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:

{
  "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"
  }
}

Valores em unidades

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:

{
  "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 24 horas;
  • 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 ignoram o header, porque já são idempotentes (conteúdo repetido não é reindexado nem cobrado).

Rate limit

Cada token de API pode fazer 60 requisições por minuto nas rotas /v1/tools/*. Toda resposta informa a situação:

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.

// 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),
  }),
);
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:

ItemValor
Leituras grátis por dia2.000
Contas premium4.000
Resetmeia-noite UTC

Toda leitura medida devolve a posição na franquia:

X-API-Quota-Limit: 2000
X-API-Quota-Used: 847
X-API-Quota-Remaining: 1153

Requisições feitas pelo dashboard não entram na franquia nem são cobradas.

Leituras medidas

Depois de esgotar a franquia, cada leitura via API debita:

FerramentaOperaçãoCréditos após a franquia
QR DinâmicoGETListar QRs0,005
QR DinâmicoGETDetalhe0,01
QR DinâmicoGETVerificar disponibilidade de slug0,005
QR DinâmicoGETOverview de analytics0,03
QR DinâmicoGETScans por período0,03
QR DinâmicoGETDistribuição por dispositivo0,03
QR DinâmicoGETHistórico de scans0,01
QR DinâmicoGETExportar scans em CSV0,01
QR DinâmicoGETListar pastas0,005
QR DinâmicoGETListar lotes0,005
QR DinâmicoGETDetalhe do lote0,01
PDF ConvertGETListar conversões0,005
PDF ConvertGETStatus da conversão0,01
PDF ConvertGETBaixar arquivo0,03
PDF ConvertGETListar batches0,005
PDF ConvertGETDetalhe do batch0,01
PDF ConvertGETDownload ZIP do batch0,03
Verificação de URLGETVerificar URL0,01
Verificação de URLGETFavicon de um domínio0,002
Webhook RelayGETListar mensagens0,002
Webhook RelayGETConsultar mensagem0,002
RealtimeGETConsultar histórico do canal0,002
Extrator de erro de CIPOSTExtrair o erro de um log0,01
Página web para MarkdownPOSTConverter página em Markdown0,01
NotificaçõesPOSTEnviar notificação0,03
ÁudioGETListar jobs de áudio0,005
ÁudioGETStatus e resultado do job0,01

Operações de escrita sem custo próprio (PATCH, ativar, desativar, DELETE) e o dry-run de criação são sempre gratuitos.

Evite polling

A franquia cobre a maioria das integrações. Para acompanhar mudanças de estado, prefira webhooks em vez de consultar o status repetidamente.

Timeouts

  • Maioria das rotas: 30 segundos.
  • Downloads de arquivo: 2 minutos.

Atualizado em