Respawn Tech Dev

QR Dinâmico

API de QR codes dinâmicos — criação, gestão, analytics, custos e redirect público.

Crie QR codes com URL de destino editável, design customizável e analytics em tempo real. A cobrança é por criação e por scan; o redirect público fica em /r/{short_id}.

Base path
/v1/tools/image/qr-code/dynamic
Escopos
tools:qr:dynamic:readtools:qr:dynamic:managetools:qr:domains:managetools:manage

Visão geral

O QR dinâmico separa a imagem impressa (link curto estável) do destino (editável sem reimprimir). Fluxo típico:

  1. POST com X-Dry-Run: true para validar o payload e ver o custo.
  2. POST definitivo com X-Idempotency-Key → recebe a imagem do QR, o resource_id e o slug.
  3. Use a imagem retornada (ou gere a sua apontando para full_url, disponível em GET /{id}).
  4. Acompanhe os scans via analytics ou pelo webhook qr.dynamic.scanned. O webhook é de melhor esforço (em picos de tráfego, alguns eventos podem não ser enviados); para contagens exatas, use o analytics.

Redirect público cobra por scan

GET /r/{short_id} não usa autenticação, mas cada scan pode debitar créditos da conta dona do QR. Sem saldo, o QR pode ser desativado. Scans repetidos do mesmo visitante no mesmo QR em até 30 segundos são cobrados uma única vez: continuam redirecionando, mas não entram no analytics, não disparam o webhook nem consomem o scan_limit.

OperaçãoRota
Criar QR dinâmicoPOST/
Listar QRsGET/
DetalheGET/{id}
Atualizar destino, nome ou designPATCH/{id}
ReativarPUT/{id}/activate
DesativarPUT/{id}/deactivate
ExcluirDELETE/{id}
Verificar disponibilidade de slugGET/check-slug/{slug}
Overview de analyticsGET/{id}/analytics
Scans por períodoGET/{id}/analytics/period
Distribuição por dispositivoGET/{id}/analytics/device
Histórico de scansGET/{id}/scans
Exportar scans em CSVGET/{id}/scans/export
Apagar o histórico de scansDELETE/{id}/scans
Editar vários QRs de uma vezPOST/bulk
Listar pastasGET/v1/tools/image/qr-code/folders
Criar pastaPOST/v1/tools/image/qr-code/folders
Renomear pastaPATCH/v1/tools/image/qr-code/folders/{id}
Excluir pastaDELETE/v1/tools/image/qr-code/folders/{id}
Criar QRs em lotePOST/v1/tools/image/qr-code/batches
Listar lotesGET/v1/tools/image/qr-code/batches
Detalhe do loteGET/v1/tools/image/qr-code/batches/{id}
Listar domínios própriosGET/v1/tools/image/qr-code/domains
Detalhe do domínioGET/v1/tools/image/qr-code/domains/{id}
Cadastrar domínio próprioPOST/v1/tools/image/qr-code/domains
Editar páginas de fallback do domínioPATCH/v1/tools/image/qr-code/domains/{id}
Verificar o domínio agoraPOST/v1/tools/image/qr-code/domains/{id}/refresh
Remover domínioDELETE/v1/tools/image/qr-code/domains/{id}
Redirect públicoGET/r/{short_id}

Custos

Criação — cobrada uma vez no POST (opções ligadas depois num PATCH custam o mesmo)

ItemCréditosNotas
Base (com imagem)1,0—
Base sem gerar imagem0,5skip_image_generation: true
Slug customizado+35,03–50 caracteres [a-zA-Z0-9-]
Gradiente no design+5,0—
Olhos customizados+2,0eye_style ou eye_dot_style shield/flower
Controle de expiração+2,0expires_at
Limite de scans+2,0scan_limit
Processamento de logo+4,0—
Encaminhamento UTM+59,0Repassa a query string ao destino
Regras de redirect+99,0redirect_rules
Teste A/B+79,0ab_variants
Senha+49,0password
Pixels de rastreamento+59,0pixels (GA4, Meta, GTM)
Redirect de expirado/esgotado+29,0expired_url ou limit_reached_url
Página hospedada+49,0content (vCard, arquivo, links, cardápio, Wi-Fi, loja de apps)
Render PNG extra+1,5formato png ou pdf (cobrado uma vez)

Domínio próprio

ItemCréditosNotas
Domínio ativo (por 30 dias)99,0Cobrado ao ativar e a cada 30 dias; domínio pendente, com falha ou suspenso não é cobrado

Lote

ItemCréditosNotas
Linha válida do lote1,0Desconto por volume a partir de 100, 500 e 1.000 linhas; linha que falha depois é estornada

Por scan — cobrado de forma assíncrona no redirect público — O valor por scan depende da quantidade de slots de armazenamento pagos na conta. Scans repetidos do mesmo visitante no mesmo QR em até 30 s são cobrados uma única vez.

ItemCréditos/scan
0–1 slots pagos0,05
2–20 slots pagos0,045
21–40 slots pagos0,04
41–80 slots pagos0,035
81+ slots pagos0,025

Armazenamento

ItemCréditosNotas
Primeiro QR dinâmico0,0Não ocupa slot pago
Slot (grupo de 3 QRs)3,0 / diaQRs desativados contam até serem excluídos

Leituras via API (listagem, detalhe, analytics, scans, verificação de slug) seguem a franquia diária. PATCH, ativar, desativar e DELETE são gratuitos.

Criação e gestão

Criar QR dinâmico

POST/v1/tools/image/qr-code/dynamic
Custo
Variável (ver custos)
Dry-run
Suportado (X-Dry-Run: true)
Autenticação
qualquer um detools:qr:dynamic:managetools:manage

Campos principais: name e target_url (o target_url é opcional quando há content). As cores e o estilo ficam no próprio corpo (foreground_color, background_color, module_style, eye_style, size, logo_data_url…). Opcionais: custom_slug, custom_domain_id (domínio próprio), expires_at, scan_limit, skip_image_generation, utm_forwarding, folder_id e tags (organização), frame e dpi (impressão) e as opções de comportamento do scan — cada uma pode somar custo (veja Custos). response_types escolhe os formatos da imagem (svg, png, pdf e eps; padrão svg): svg e eps vêm como texto, png e pdf em base64.

A resposta traz as imagens, o resource_id (id do QR) e o slug. Com skip_image_generation: true, vem um único item só com resource_id e slug.

curl -sS -X POST 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic' \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \
  -H 'Content-Type: application/json' \
  -d '{
"name": "Campanha verão",
"target_url": "https://loja.example.com/promo",
"custom_slug": "promo-verao",
"foreground_color": "#000000",
"background_color": "#ffffff"
}'
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic', {
  method: 'POST',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
    'X-Idempotency-Key': '550e8400-e29b-41d4-a716-446655440000',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
"name": "Campanha verão",
"target_url": "https://loja.example.com/promo",
"custom_slug": "promo-verao",
"foreground_color": "#000000",
"background_color": "#ffffff"
}),
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.post(
    "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
        "X-Idempotency-Key": "550e8400-e29b-41d4-a716-446655440000",
    },
    json={
"name": "Campanha verão",
"target_url": "https://loja.example.com/promo",
"custom_slug": "promo-verao",
"foreground_color": "#000000",
"background_color": "#ffffff"
},
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
        'X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => <<<'JSON'
{
"name": "Campanha verão",
"target_url": "https://loja.example.com/promo",
"custom_slug": "promo-verao",
"foreground_color": "#000000",
"background_color": "#ffffff"
}
JSON,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"
)

func main() {
	req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic", strings.NewReader(`{
"name": "Campanha verão",
"target_url": "https://loja.example.com/promo",
"custom_slug": "promo-verao",
"foreground_color": "#000000",
"background_color": "#ffffff"
}`))
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
	req.Header.Set("X-Idempotency-Key", "550e8400-e29b-41d4-a716-446655440000")
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 201 — um item por formato pedido em response_types (padrão ["svg"])

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": [
    {
      "body": "<svg xmlns=\"http://www.w3.org/2000/svg\" …>…</svg>",
      "content_type": "image/svg+xml",
      "format": "svg",
      "resource_id": "8f3a2b1c-4d5e-6f70-8192-a3b4c5d6e7f8",
      "slug": "promo-verao"
    },
    {
      "body": "iVBORw0KGgoAAAANSUhEUgAA…",
      "content_type": "image/png",
      "format": "png",
      "resource_id": "8f3a2b1c-4d5e-6f70-8192-a3b4c5d6e7f8",
      "slug": "promo-verao"
    }
  ]
}

Listar QRs

GET/v1/tools/image/qr-code/dynamic
Custo
Franquia diária, depois 0,005 crédito/leitura via API
Dry-run
Suportado (X-Dry-Run: true)
Autenticação
qualquer um detools:qr:dynamic:readtools:qr:dynamic:managetools:readtools:manage

Query: limit (padrão 20, máx. 100), offset, search, folder_id (um UUID, ou none para os QRs sem pasta), tags (separadas por vírgula; o QR precisa ter todas) e status (active ou paused).

curl -sS -X GET 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic?limit=20&offset=0' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic?limit=20&offset=0', {
  method: 'GET',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.get(
    "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic?limit=20&offset=0",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic?limit=20&offset=0');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic?limit=20&offset=0", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "items": [
      {
        "id": "8f3a2b1c-4d5e-6f70-8192-a3b4c5d6e7f8",
        "name": "Campanha verão",
        "target_url": "https://loja.example.com/promo",
        "full_url": "https://api.respawntech.dev/r/promo-verao",
        "utm_forwarding": false,
        "scan_limit": 1000,
        "total_scans": 42,
        "updated_at": "2026-09-20T14:03:11Z",
        "design": {
          "size": 512,
          "foreground_color": "#000000",
          "background_color": "#FFFFFF",
          "transparent": false,
          "recovery_level": "M",
          "module_style": "dots",
          "eye_style": "rounded"
        },
        "resource": {
          "slug": "promo-verao",
          "status": "active",
          "created_at": "2026-09-01T10:00:00Z",
          "expires_at": "2026-12-31T23:59:59Z"
        }
      }
    ],
    "total": 1
  }
}

Detalhe

GET/v1/tools/image/qr-code/dynamic/{id}
Custo
Franquia diária, depois 0,01 crédito/leitura via API
Dry-run
Suportado (X-Dry-Run: true)
Autenticação
qualquer um detools:qr:dynamic:readtools:qr:dynamic:managetools:readtools:manage
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000', {
  method: 'GET',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.get(
    "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "id": "8f3a2b1c-4d5e-6f70-8192-a3b4c5d6e7f8",
    "name": "Campanha verão",
    "target_url": "https://loja.example.com/promo",
    "full_url": "https://api.respawntech.dev/r/promo-verao",
    "utm_forwarding": false,
    "scan_limit": 1000,
    "total_scans": 42,
    "updated_at": "2026-09-20T14:03:11Z",
    "design": {
      "size": 512,
      "foreground_color": "#000000",
      "background_color": "#FFFFFF",
      "transparent": false,
      "recovery_level": "M",
      "module_style": "dots",
      "eye_style": "rounded"
    },
    "resource": {
      "slug": "promo-verao",
      "status": "active",
      "created_at": "2026-09-01T10:00:00Z",
      "expires_at": "2026-12-31T23:59:59Z"
    }
  }
}

Atualizar destino, nome ou design

PATCH/v1/tools/image/qr-code/dynamic/{id}
Custo
Grátis, exceto ao ligar opções pagas (ver custos)
Dry-run
Suportado (X-Dry-Run: true)
Autenticação
qualquer um detools:qr:dynamic:managetools:manage

Mudar nome, destino e cores é grátis. Ligar uma opção paga que estava desligada — utm_forwarding, scan_limit (de 0 para um valor), gradiente (foreground_color_end), olhos customizados (eye_style ou eye_dot_style shield/flower) ou logo — cobra o mesmo valor da criação, uma única vez por atualização. Alterar uma opção já ligada ou desligá-la é grátis, sem estorno. Aceita X-Dry-Run, X-Include-Cost e X-Idempotency-Key; sem saldo a resposta é 402 e nada muda, uma atualização idêntica já cobrada responde 409, e uma cor inválida é recusada (400) antes de cobrar. A nova target_url passa pelas verificações anti-malware e anti-loop. Como em ativar, desativar e excluir, a resposta é apenas uma confirmação — consulte GET /{id} para ver o recurso atualizado.

curl -sS -X PATCH 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000' \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
"name": "Campanha atualizada",
"target_url": "https://loja.example.com/nova-promo"
}'
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000', {
  method: 'PATCH',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
"name": "Campanha atualizada",
"target_url": "https://loja.example.com/nova-promo"
}),
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.patch(
    "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    json={
"name": "Campanha atualizada",
"target_url": "https://loja.example.com/nova-promo"
},
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => <<<'JSON'
{
"name": "Campanha atualizada",
"target_url": "https://loja.example.com/nova-promo"
}
JSON,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"
)

func main() {
	req, err := http.NewRequest("PATCH", "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000", strings.NewReader(`{
"name": "Campanha atualizada",
"target_url": "https://loja.example.com/nova-promo"
}`))
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "available": true
  }
}

Reativar

PUT/v1/tools/image/qr-code/dynamic/{id}/activate
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools:qr:dynamic:managetools:manage
curl -sS -X PUT 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/activate' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/activate', {
  method: 'PUT',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.put(
    "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/activate",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/activate');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("PUT", "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/activate", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "available": true
  }
}

Desativar

PUT/v1/tools/image/qr-code/dynamic/{id}/deactivate
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools:qr:dynamic:managetools:manage

Um QR desativado mostra uma página de QR inativo no redirect público e continua ocupando slot até ser excluído.

curl -sS -X PUT 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/deactivate' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/deactivate', {
  method: 'PUT',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.put(
    "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/deactivate",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/deactivate');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("PUT", "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/deactivate", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "available": true
  }
}

Excluir

DELETE/v1/tools/image/qr-code/dynamic/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools:qr:dynamic:managetools:manage

Encerra a cobrança de armazenamento do slot.

curl -sS -X DELETE 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000', {
  method: 'DELETE',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.delete(
    "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("DELETE", "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "available": true
  }
}

Verificar disponibilidade de slug

GET/v1/tools/image/qr-code/dynamic/check-slug/{slug}
Custo
Franquia diária, depois 0,005 crédito/leitura via API
Dry-run
Suportado (X-Dry-Run: true)
Autenticação
qualquer um detools:qr:dynamic:readtools:qr:dynamic:managetools:readtools:manage
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/check-slug/promo-verao' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/check-slug/promo-verao', {
  method: 'GET',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.get(
    "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/check-slug/promo-verao",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/check-slug/promo-verao');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/check-slug/promo-verao", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "available": false
  }
}

Analytics

Overview de analytics

GET/v1/tools/image/qr-code/dynamic/{id}/analytics
Custo
Franquia diária, depois 0,03 crédito/leitura via API
Dry-run
Suportado (X-Dry-Run: true)
Autenticação
qualquer um detools:qr:dynamic:readtools:qr:dynamic:managetools:readtools:manage

Agregações com cache. Query period: last_7d, last_30d (padrão), last_90d ou all; valores desconhecidos contam como 30 dias. scan_history traz os 20 scans mais recentes.

curl -sS -X GET 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/analytics?period=last_30d' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/analytics?period=last_30d', {
  method: 'GET',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.get(
    "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/analytics?period=last_30d",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/analytics?period=last_30d');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/analytics?period=last_30d", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "period": "last_30d",
    "total_scans": 42,
    "total_cost": 21000,
    "period_scans": 30,
    "period_cost": 15000,
    "unique_scans": 18,
    "period_stats": [
      {
        "date": "2026-09-20",
        "count": 7
      }
    ],
    "device_stats": [
      {
        "device": "Mobile",
        "count": 25
      },
      {
        "device": "Desktop",
        "count": 5
      }
    ],
    "country_stats": [
      {
        "location": "BR",
        "count": 20,
        "iso_code": "BR"
      }
    ],
    "city_stats": [
      {
        "location": "São Paulo",
        "count": 12
      }
    ],
    "os_stats": [
      {
        "location": "Android",
        "count": 15
      }
    ],
    "browser_stats": [
      {
        "location": "Chrome",
        "count": 19
      }
    ],
    "referrer_stats": [
      {
        "location": "instagram.com",
        "count": 4
      }
    ],
    "heatmap_stats": [
      {
        "day": 1,
        "hour": 14,
        "count": 3
      }
    ],
    "scan_history": [
      {
        "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
        "country": "BR",
        "city": "São Paulo",
        "os": "Android",
        "browser": "Chrome",
        "user_agent": "Mozilla/5.0 (Linux; Android 14) …",
        "referer": "https://instagram.com/",
        "scanned_at": "2026-09-20T18:22:04Z",
        "location": "São Paulo, BR",
        "device": "Mobile"
      }
    ]
  }
}

Scans por período

GET/v1/tools/image/qr-code/dynamic/{id}/analytics/period
Custo
Franquia diária, depois 0,03 crédito/leitura via API
Dry-run
Suportado (X-Dry-Run: true)
Autenticação
qualquer um detools:qr:dynamic:readtools:qr:dynamic:managetools:readtools:manage

Retorna os totais e period_stats (scans por dia). Os campos period, period_scans, period_cost e unique_scans vêm zerados nesta rota — use o overview se precisar deles.

curl -sS -X GET 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/analytics/period?period=last_30d' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/analytics/period?period=last_30d', {
  method: 'GET',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.get(
    "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/analytics/period?period=last_30d",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/analytics/period?period=last_30d');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/analytics/period?period=last_30d", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "period": "",
    "total_scans": 42,
    "total_cost": 21000,
    "period_scans": 0,
    "period_cost": 0,
    "unique_scans": 0,
    "period_stats": [
      {
        "date": "2026-09-19",
        "count": 5
      },
      {
        "date": "2026-09-20",
        "count": 7
      }
    ]
  }
}

Distribuição por dispositivo

GET/v1/tools/image/qr-code/dynamic/{id}/analytics/device
Custo
Franquia diária, depois 0,03 crédito/leitura via API
Dry-run
Suportado (X-Dry-Run: true)
Autenticação
qualquer um detools:qr:dynamic:readtools:qr:dynamic:managetools:readtools:manage

device é Mobile, Desktop ou Tablet. Assim como em scans por período, os campos de período vêm zerados.

curl -sS -X GET 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/analytics/device?period=last_30d' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/analytics/device?period=last_30d', {
  method: 'GET',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.get(
    "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/analytics/device?period=last_30d",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/analytics/device?period=last_30d');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/analytics/device?period=last_30d", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "period": "",
    "total_scans": 42,
    "total_cost": 21000,
    "period_scans": 0,
    "period_cost": 0,
    "unique_scans": 0,
    "device_stats": [
      {
        "device": "Mobile",
        "count": 30
      },
      {
        "device": "Desktop",
        "count": 10
      },
      {
        "device": "Tablet",
        "count": 2
      }
    ]
  }
}

Histórico de scans

GET/v1/tools/image/qr-code/dynamic/{id}/scans
Custo
Franquia diária, depois 0,01 crédito/leitura via API
Dry-run
Suportado (X-Dry-Run: true)
Autenticação
qualquer um detools:qr:dynamic:readtools:qr:dynamic:managetools:readtools:manage

Paginação: limit (padrão 50, máx. 100), offset e period (padrão: últimos 30 dias). location vem como Unknown quando cidade e país são desconhecidos.

curl -sS -X GET 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/scans?limit=50&offset=0&period=last_30d' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/scans?limit=50&offset=0&period=last_30d', {
  method: 'GET',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.get(
    "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/scans?limit=50&offset=0&period=last_30d",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/scans?limit=50&offset=0&period=last_30d');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/scans?limit=50&offset=0&period=last_30d", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "items": [
      {
        "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
        "country": "BR",
        "city": "São Paulo",
        "os": "Android",
        "browser": "Chrome",
        "user_agent": "Mozilla/5.0 (Linux; Android 14) …",
        "referer": "https://instagram.com/",
        "scanned_at": "2026-09-20T18:22:04Z",
        "location": "São Paulo, BR",
        "device": "Mobile"
      }
    ],
    "total": 42
  }
}

Redirect público

GET/r/{short_id}
Custo
Por scan (ver custos)
Dry-run
Não
Autenticação
Pública (sem token)
  • Ativo: 307 para a target_url (com a query string repassada, se UTM forwarding estiver ativo). Em contas sem plano premium, o scan passa antes por uma página intermediária da marca que redireciona em 3 segundos.
  • Inexistente, inativo, expirado, sem saldo, acima do limite de scans ou com destino inseguro: redireciona para a página de QR indisponível, com o motivo em ?reason= (unknown, inactive, expired, bankrupt, limit_reached, safety_risk, rate_limited).
  • Muitos acessos do mesmo visitante: acima de 600 por minuto (somando todos os QRs), redireciona para a página de QR indisponível com ?reason=rate_limited. Acima de 30 scans por minuto no mesmo QR, o redirect continua, mas os scans extras não são contados nem cobrados.
  • As respostas nunca são cacheadas (Cache-Control: no-store).
curl -sS -I 'https://api.respawntech.dev/r/promo-verao'

Resposta 307 — Location: <target_url>; contas sem plano premium recebem 200 com uma página HTML intermediária que redireciona em 3 s

Domínio próprio

Em vez de api.respawntech.dev/r/promo-verao, o QR pode viver em https://qr.suaempresa.com/promo-verao. Você aponta um subdomínio seu para a Respawn Tech com um registro CNAME; o certificado HTTPS é emitido automaticamente (Cloudflare for SaaS). O hostname precisa ser um subdomínio com pelo menos três rótulos (qr.suaempresa.com): um domínio raiz (apex) não aceita CNAME, e porta, caminho, IP e curinga são recusados.

O escopo é tools:qr:domains:manage (ou tools:qr:dynamic:manage / tools:manage). O limite de domínios depende do plano (qr_domain.plan_limit) e um domínio ativo é cobrado a cada 30 dias (veja Custos).

Cadastre o domínio

curl -sS -X POST 'https://api.respawntech.dev/v1/tools/image/qr-code/domains' \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "hostname": "qr.suaempresa.com",
    "validation_method": "http",
    "root_url": "https://www.suaempresa.com"
  }'

validation_method é http (padrão) ou txt: é como o certificado é validado. A resposta (201) traz status: "pending" e a lista records com tudo o que você precisa publicar. Guarde o id.

Publique o CNAME

No painel de DNS do seu domínio, crie o registro do item com purpose: "routing". O destino (value) vem na resposta; não o copie de exemplos.

TipoNomeValor
CNAMEqr (ou qr.suaempresa.com)o value do registro routing

No Cloudflare, crie o CNAME na zona suaempresa.com e deixe o proxy desligado (nuvem cinza, "somente DNS") até o domínio ficar active; só então, se quiser, ligue o proxy. Em outros provedores, é um CNAME comum para o subdomínio.

Valide o certificado

Se você escolheu http, o registro purpose: "certificate" com type: "HTTP" é validado sozinho assim que o CNAME propagar, sem nada a publicar. Se escolheu txt, publique também os registros TXT que vierem em records (ownership e certificate), no mesmo painel de DNS.

Verifique até ficar ativo

A plataforma consulta o domínio periodicamente; para não esperar, force a verificação:

curl -sS -X POST 'https://api.respawntech.dev/v1/tools/image/qr-code/domains/$DOMAIN_ID/refresh' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"

O status passa de pending para active (a cobrança do período acontece nesse momento). failed indica que o provedor recusou o hostname (veja last_error); suspended, que o período não pôde ser cobrado — os scans vão para a página de fallback até a próxima cobrança bem-sucedida.

Crie QRs no domínio

Mande o id do domínio em custom_domain_id. Um domínio ainda pending já é aceito, para você imprimir enquanto o DNS propaga, mas os scans só redirecionam depois que ele fica active.

curl -sS -X POST 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic' \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Cartaz da loja",
    "target_url": "https://loja.example.com/promo",
    "custom_slug": "promo-verao",
    "custom_domain_id": "'"$DOMAIN_ID"'"
  }'

O full_url do QR passa a ser https://qr.suaempresa.com/promo-verao. O slug só precisa ser único dentro do domínio.

root_url é para onde vai a raiz do domínio (https://qr.suaempresa.com/) e not_found_url, para onde vão slugs desconhecidos; sem eles, vale a página de fallback da plataforma. Nenhum dos dois pode apontar de volta para o próprio domínio. Só root_url e not_found_url mudam depois do cadastro (PATCH); para trocar o hostname, remova e cadastre de novo.

Remover um domínio

Um domínio com QRs ainda vinculados não pode ser removido (qr_domain.in_use). Exclua os QRs primeiro; depois de removido o domínio, o full_url deles deixa de funcionar.

Listar domínios próprios

GET/v1/tools/image/qr-code/domains
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools:qr:domains:managetools:qr:dynamic:readtools:qr:dynamic:managetools:readtools:manage
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/image/qr-code/domains' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/domains', {
  method: 'GET',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.get(
    "https://api.respawntech.dev/v1/tools/image/qr-code/domains",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/domains');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/image/qr-code/domains", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": [
    {
      "id": "5c2a8e71-0b4d-4e96-8f13-a7d6c9b0e214",
      "hostname": "qr.suaempresa.com",
      "status": "pending",
      "ssl_status": "pending_validation",
      "validation_method": "http",
      "records": [
        {
          "purpose": "routing",
          "type": "CNAME",
          "name": "qr.suaempresa.com",
          "value": "domains.respawntech.dev"
        },
        {
          "purpose": "certificate",
          "type": "HTTP",
          "name": "http://qr.suaempresa.com/.well-known/pki-validation/4d2f.txt",
          "value": "4d2f9a1c-6b7e-4f0a-8c3d-2e1b5a9d7c60"
        }
      ],
      "root_url": "https://www.suaempresa.com",
      "created_at": "2026-09-20T14:00:00Z"
    }
  ]
}

Detalhe do domínio

GET/v1/tools/image/qr-code/domains/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools:qr:domains:managetools:qr:dynamic:readtools:qr:dynamic:managetools:readtools:manage
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000', {
  method: 'GET',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.get(
    "https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "id": "5c2a8e71-0b4d-4e96-8f13-a7d6c9b0e214",
    "hostname": "qr.suaempresa.com",
    "status": "pending",
    "ssl_status": "pending_validation",
    "validation_method": "http",
    "records": [
      {
        "purpose": "routing",
        "type": "CNAME",
        "name": "qr.suaempresa.com",
        "value": "domains.respawntech.dev"
      },
      {
        "purpose": "certificate",
        "type": "HTTP",
        "name": "http://qr.suaempresa.com/.well-known/pki-validation/4d2f.txt",
        "value": "4d2f9a1c-6b7e-4f0a-8c3d-2e1b5a9d7c60"
      }
    ],
    "root_url": "https://www.suaempresa.com",
    "created_at": "2026-09-20T14:00:00Z"
  }
}

Cadastrar domínio próprio

POST/v1/tools/image/qr-code/domains
Custo
Por 30 dias, enquanto o domínio estiver ativo (ver custos)
Dry-run
Não
Autenticação
qualquer um detools:qr:domains:managetools:qr:dynamic:managetools:manage
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/image/qr-code/domains' \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
"hostname": "qr.suaempresa.com",
"validation_method": "http",
"root_url": "https://www.suaempresa.com"
}'
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/domains', {
  method: 'POST',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
"hostname": "qr.suaempresa.com",
"validation_method": "http",
"root_url": "https://www.suaempresa.com"
}),
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.post(
    "https://api.respawntech.dev/v1/tools/image/qr-code/domains",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    json={
"hostname": "qr.suaempresa.com",
"validation_method": "http",
"root_url": "https://www.suaempresa.com"
},
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/domains');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => <<<'JSON'
{
"hostname": "qr.suaempresa.com",
"validation_method": "http",
"root_url": "https://www.suaempresa.com"
}
JSON,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"
)

func main() {
	req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/image/qr-code/domains", strings.NewReader(`{
"hostname": "qr.suaempresa.com",
"validation_method": "http",
"root_url": "https://www.suaempresa.com"
}`))
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 201

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "id": "5c2a8e71-0b4d-4e96-8f13-a7d6c9b0e214",
    "hostname": "qr.suaempresa.com",
    "status": "pending",
    "ssl_status": "pending_validation",
    "validation_method": "http",
    "records": [
      {
        "purpose": "routing",
        "type": "CNAME",
        "name": "qr.suaempresa.com",
        "value": "domains.respawntech.dev"
      },
      {
        "purpose": "certificate",
        "type": "HTTP",
        "name": "http://qr.suaempresa.com/.well-known/pki-validation/4d2f.txt",
        "value": "4d2f9a1c-6b7e-4f0a-8c3d-2e1b5a9d7c60"
      }
    ],
    "root_url": "https://www.suaempresa.com",
    "created_at": "2026-09-20T14:00:00Z"
  }
}

Editar páginas de fallback do domínio

PATCH/v1/tools/image/qr-code/domains/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools:qr:domains:managetools:qr:dynamic:managetools:manage

Mande "" para limpar um dos campos.

curl -sS -X PATCH 'https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000' \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
"root_url": "https://www.suaempresa.com",
"not_found_url": "https://www.suaempresa.com/ops"
}'
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000', {
  method: 'PATCH',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
"root_url": "https://www.suaempresa.com",
"not_found_url": "https://www.suaempresa.com/ops"
}),
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.patch(
    "https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    json={
"root_url": "https://www.suaempresa.com",
"not_found_url": "https://www.suaempresa.com/ops"
},
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => <<<'JSON'
{
"root_url": "https://www.suaempresa.com",
"not_found_url": "https://www.suaempresa.com/ops"
}
JSON,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"
)

func main() {
	req, err := http.NewRequest("PATCH", "https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000", strings.NewReader(`{
"root_url": "https://www.suaempresa.com",
"not_found_url": "https://www.suaempresa.com/ops"
}`))
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "id": "5c2a8e71-0b4d-4e96-8f13-a7d6c9b0e214",
    "hostname": "qr.suaempresa.com",
    "status": "pending",
    "ssl_status": "pending_validation",
    "validation_method": "http",
    "records": [
      {
        "purpose": "routing",
        "type": "CNAME",
        "name": "qr.suaempresa.com",
        "value": "domains.respawntech.dev"
      },
      {
        "purpose": "certificate",
        "type": "HTTP",
        "name": "http://qr.suaempresa.com/.well-known/pki-validation/4d2f.txt",
        "value": "4d2f9a1c-6b7e-4f0a-8c3d-2e1b5a9d7c60"
      }
    ],
    "root_url": "https://www.suaempresa.com",
    "created_at": "2026-09-20T14:00:00Z",
    "not_found_url": "https://www.suaempresa.com/ops"
  }
}

Verificar o domínio agora

POST/v1/tools/image/qr-code/domains/{id}/refresh
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools:qr:domains:managetools:qr:dynamic:managetools:manage
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000/refresh' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000/refresh', {
  method: 'POST',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.post(
    "https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000/refresh",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000/refresh');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000/refresh", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "id": "5c2a8e71-0b4d-4e96-8f13-a7d6c9b0e214",
    "hostname": "qr.suaempresa.com",
    "status": "active",
    "ssl_status": "active",
    "validation_method": "http",
    "records": [
      {
        "purpose": "routing",
        "type": "CNAME",
        "name": "qr.suaempresa.com",
        "value": "domains.respawntech.dev"
      },
      {
        "purpose": "certificate",
        "type": "HTTP",
        "name": "http://qr.suaempresa.com/.well-known/pki-validation/4d2f.txt",
        "value": "4d2f9a1c-6b7e-4f0a-8c3d-2e1b5a9d7c60"
      }
    ],
    "root_url": "https://www.suaempresa.com",
    "created_at": "2026-09-20T14:00:00Z",
    "verified_at": "2026-09-20T14:20:00Z",
    "billed_until": "2026-10-20T14:20:00Z"
  }
}

Remover domínio

DELETE/v1/tools/image/qr-code/domains/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools:qr:domains:managetools:qr:dynamic:managetools:manage
curl -sS -X DELETE 'https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000', {
  method: 'DELETE',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.delete(
    "https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("DELETE", "https://api.respawntech.dev/v1/tools/image/qr-code/domains/550e8400-e29b-41d4-a716-446655440000", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 204 — sem corpo; recusado com qr_domain.in_use se ainda houver QRs nele

Comportamento do scan

Por padrão o scan é um redirect para a target_url. Estes campos, aceitos no POST e no PATCH, mudam o que acontece no scan. A avaliação é nesta ordem: senha (o scan só segue depois dela), regras (a primeira que casar vence), teste A/B (para os scans que nenhuma regra pegou) e, por fim, a target_url. Cada opção é cobrada uma vez (veja Custos).

Num PATCH, cada campo enviado substitui a sua parte: [] limpa as regras ou as variantes, {} limpa os pixels e "" limpa uma URL ou a senha.

Regras de redirect

redirect_rules (até 20) manda o scan para uma URL própria quando todas as condições da regra batem. Uma regra sem nenhuma condição é recusada.

CampoSignificado
idNome da regra ([a-zA-Z0-9_-], até 40); aparece nos scans e em rule_stats do analytics
target_urlDestino quando a regra casa (URL absoluta)
countriesPaíses ISO 3166-1 alfa-2 em maiúsculas (["BR", "PT"])
devicesmobile, tablet, desktop
osios, android, windows, macos, linux, other
languagesIdioma preferido do visitante: pt casa pt e pt-br; pt-br só pt-br
daysDias da semana, 0 (domingo) a 6 (sábado), no timezone
start_time, end_timeJanela do dia (HH:MM); se o fim vier antes do início, atravessa a meia-noite. Os dois ou nenhum
timezoneFuso IANA (America/Sao_Paulo); UTC se vazio
from, untilPeríodo em que a regra vale (data e hora RFC 3339)

Cada lista aceita até 50 valores. Dentro de uma regra as condições se somam (E); dentro de uma lista, basta um valor (OU).

Teste A/B

ab_variants (de 2 a 10) divide, por peso, os scans que nenhuma regra pegou. Cada variante tem id único, target_url e weight de 1 a 1000; a fatia de uma variante é o seu peso dividido pela soma. O analytics devolve variant_stats com os scans de cada variante.

Senha

password (de 4 a 72 caracteres) esconde o QR atrás de uma página de senha. A senha é guardada com hash; as respostas trazem apenas scan_behavior.has_password. Depois de digitar a senha certa, o visitante segue o fluxo normal e essa página não fica em cache. Após 5 senhas erradas, o visitante fica bloqueado naquele QR por 15 minutos.

Pixels

pixels dispara as tags numa página leve antes do redirect: ga4_measurement_id (G-XXXXXXX), meta_pixel_id (só dígitos) e gtm_container_id (GTM-XXXXXX).

Redirect de expirado e esgotado

expired_url e limit_reached_url (URLs absolutas) substituem a página de QR indisponível quando o QR expirou (expires_at) ou usou todo o scan_limit.

Exemplo completo

Copie, troque as URLs e rode:

curl -sS -X POST 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic' \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Campanha com regras",
    "target_url": "https://loja.example.com/global",
    "redirect_rules": [
      {
        "id": "br-mobile",
        "target_url": "https://loja.example.com/br",
        "countries": ["BR"],
        "devices": ["mobile"]
      },
      {
        "id": "horario-comercial",
        "target_url": "https://loja.example.com/atendimento",
        "days": [1, 2, 3, 4, 5],
        "start_time": "09:00",
        "end_time": "18:00",
        "timezone": "America/Sao_Paulo"
      }
    ],
    "ab_variants": [
      { "id": "a", "target_url": "https://loja.example.com/lp-a", "weight": 50 },
      { "id": "b", "target_url": "https://loja.example.com/lp-b", "weight": 50 }
    ],
    "pixels": { "ga4_measurement_id": "G-ABCD1234" },
    "expired_url": "https://loja.example.com/encerrada",
    "limit_reached_url": "https://loja.example.com/esgotado"
  }'

Para tirar tudo depois, sem recriar o QR (troque $QR_ID pelo resource_id da criação):

curl -sS -X PATCH "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/$QR_ID" \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{ "redirect_rules": [], "ab_variants": [], "pixels": {}, "password": "" }'

GET /{id} devolve a configuração em scan_behavior (redirect_rules, ab_variants, has_password, pixels, expired_url, limit_reached_url, content).

Conteúdo hospedado

Com content, o próprio /r/{short_id} serve uma página leve (ou um redirect calculado) no lugar da target_url. O type escolhe o formato e só o campo de mesmo nome é usado. Se uma regra de redirect ou um teste A/B casar, ele vale no lugar do conteúdo. As páginas não executam scripts e ficam em cache por 5 minutos (revalidadas por ETag). {} limpa o conteúdo num PATCH.

typeCampoO scan mostra
vcardvcard (FirstName, LastName, Org, Title, Phone, Email, Website, Address)Página de contato e download do .vcf (?format=vcf). Exige nome ou organização
filefile (url, name)Abre o arquivo ou PDF hospedado (URL absoluta)
linkslinks (title, description, avatar_url, links[] com label e url)Link na bio, de 1 a 50 links
menumenu (title, currency, sections[] → items[] com name, description, price)Cardápio, até 30 seções e 300 itens
wifiwifi (SSID, Password, Security, Hidden)Nome da rede e senha para copiar
app_storeapp_store (ios_url, android_url, fallback_url)Redirect para a App Store ou o Google Play conforme o celular; fallback_url atende desktop e sistemas desconhecidos

Os campos de vcard e wifi usam esses nomes em PascalCase, exatamente como na tabela; os demais usam snake_case. O target_url deixa de ser obrigatório. Ele assume o link do arquivo ou da loja e, nos tipos de página, cai na página de fallback da plataforma.

curl -sS -X POST 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic' \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Link na bio",
    "content": {
      "type": "links",
      "links": {
        "title": "Loja Exemplo",
        "links": [
          { "label": "Site", "url": "https://loja.example.com" },
          { "label": "Instagram", "url": "https://instagram.com/lojaexemplo" }
        ]
      }
    }
  }'

Exemplo de cartão de contato:

curl -sS -X POST 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic' \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Cartão da Ana",
    "content": {
      "type": "vcard",
      "vcard": { "FirstName": "Ana", "LastName": "Souza", "Org": "Loja Exemplo", "Phone": "+5511999990000", "Email": "ana@loja.example.com" }
    }
  }'

Molduras e impressão

frame desenha uma chamada para ação em volta do código e dpi deixa a imagem pronta para impressão. Os dois valem para qualquer response_types.

  • frame: text (até 32 caracteres; encolhe para caber), color, text_color (hex) e position (top ou bottom).
  • dpi: de 72 a 1200. No PDF a página fica do tamanho exato do QR, em vetor; no SVG a largura e a altura saem em mm; o PNG carrega a resolução de impressão no arquivo. Sem dpi, o PDF mantém o layout A4.
  • eps: formato vetorial novo, com a moldura e fonte Helvetica-Bold com acentos. Sai sem logo.

No PDF e no EPS, os olhos saem quadrados e só os estilos de módulo dots e quadrado viram vetor; o gradiente não é aplicado.

curl -sS -X POST 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic' \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Cartaz A5",
    "target_url": "https://loja.example.com/promo",
    "response_types": ["pdf", "eps"],
    "frame": { "text": "Escaneie aqui", "color": "#111111", "text_color": "#FFFFFF", "position": "bottom" },
    "dpi": 300
  }'

Organização, edição em massa e lote

Pastas e tags

folder_id guarda o QR numa pasta e tags (até 20 por QR, 40 caracteres cada, sem vírgula) o etiqueta; os dois entram no POST e no PATCH (folder_id: "" volta para a raiz, tags: [] limpa). A listagem filtra por folder_id, tags, status e search.

Listar pastas

GET/v1/tools/image/qr-code/folders
Custo
Franquia diária, depois 0,005 crédito/leitura via API
Dry-run
Suportado (X-Dry-Run: true)
Autenticação
qualquer um detools:qr:dynamic:readtools:qr:dynamic:managetools:readtools:manage
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/image/qr-code/folders' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/folders', {
  method: 'GET',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.get(
    "https://api.respawntech.dev/v1/tools/image/qr-code/folders",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/folders');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/image/qr-code/folders", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": [
    {
      "id": "3b9f6c1e-7a52-4d0b-9c41-5e2d8a7f1b30",
      "name": "Campanha de verão",
      "qr_count": 12,
      "created_at": "2026-09-01T10:00:00Z",
      "updated_at": "2026-09-01T10:00:00Z"
    }
  ]
}

Criar pasta

POST/v1/tools/image/qr-code/folders
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools:qr:dynamic:managetools:manage

O nome tem de 1 a 80 caracteres e é único na conta (dynamic_qr.folder_taken).

curl -sS -X POST 'https://api.respawntech.dev/v1/tools/image/qr-code/folders' \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{ "name": "Campanha de verão" }'
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/folders', {
  method: 'POST',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ "name": "Campanha de verão" }),
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.post(
    "https://api.respawntech.dev/v1/tools/image/qr-code/folders",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    json={ "name": "Campanha de verão" },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/folders');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => <<<'JSON'
{ "name": "Campanha de verão" }
JSON,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"
)

func main() {
	req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/image/qr-code/folders", strings.NewReader(`{ "name": "Campanha de verão" }`))
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 201

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "id": "3b9f6c1e-7a52-4d0b-9c41-5e2d8a7f1b30",
    "name": "Campanha de verão",
    "qr_count": 12,
    "created_at": "2026-09-01T10:00:00Z",
    "updated_at": "2026-09-01T10:00:00Z"
  }
}

Renomear pasta

PATCH/v1/tools/image/qr-code/folders/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools:qr:dynamic:managetools:manage
curl -sS -X PATCH 'https://api.respawntech.dev/v1/tools/image/qr-code/folders/550e8400-e29b-41d4-a716-446655440000' \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{ "name": "Verão 2027" }'
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/folders/550e8400-e29b-41d4-a716-446655440000', {
  method: 'PATCH',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ "name": "Verão 2027" }),
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.patch(
    "https://api.respawntech.dev/v1/tools/image/qr-code/folders/550e8400-e29b-41d4-a716-446655440000",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    json={ "name": "Verão 2027" },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/folders/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => <<<'JSON'
{ "name": "Verão 2027" }
JSON,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"
)

func main() {
	req, err := http.NewRequest("PATCH", "https://api.respawntech.dev/v1/tools/image/qr-code/folders/550e8400-e29b-41d4-a716-446655440000", strings.NewReader(`{ "name": "Verão 2027" }`))
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "id": "3b9f6c1e-7a52-4d0b-9c41-5e2d8a7f1b30",
    "name": "Verão 2027",
    "qr_count": 12,
    "created_at": "2026-09-01T10:00:00Z",
    "updated_at": "2026-09-01T10:00:00Z"
  }
}

Excluir pasta

DELETE/v1/tools/image/qr-code/folders/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools:qr:dynamic:managetools:manage
curl -sS -X DELETE 'https://api.respawntech.dev/v1/tools/image/qr-code/folders/550e8400-e29b-41d4-a716-446655440000' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/folders/550e8400-e29b-41d4-a716-446655440000', {
  method: 'DELETE',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.delete(
    "https://api.respawntech.dev/v1/tools/image/qr-code/folders/550e8400-e29b-41d4-a716-446655440000",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/folders/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("DELETE", "https://api.respawntech.dev/v1/tools/image/qr-code/folders/550e8400-e29b-41d4-a716-446655440000", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 204 — sem corpo; os QRs da pasta voltam para a raiz

Editar vários QRs de uma vez

POST/v1/tools/image/qr-code/dynamic/bulk
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools:qr:dynamic:managetools:manage

Uma ação em até 500 QRs, de graça. action é activate, deactivate, move (com folder_id; null manda para a raiz), set_target (com target_url), add_tags ou remove_tags (com tags). A resposta traz o resultado de cada QR em items, então uma falha não derruba as demais. Aceita X-Idempotency-Key.

curl -sS -X POST 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/bulk' \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
"ids": ["8f3a2b1c-4d5e-6f70-8192-a3b4c5d6e7f8"],
"action": "add_tags",
"tags": ["verao"]
}'
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/bulk', {
  method: 'POST',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
"ids": ["8f3a2b1c-4d5e-6f70-8192-a3b4c5d6e7f8"],
"action": "add_tags",
"tags": ["verao"]
}),
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.post(
    "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/bulk",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    json={
"ids": ["8f3a2b1c-4d5e-6f70-8192-a3b4c5d6e7f8"],
"action": "add_tags",
"tags": ["verao"]
},
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/bulk');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => <<<'JSON'
{
"ids": ["8f3a2b1c-4d5e-6f70-8192-a3b4c5d6e7f8"],
"action": "add_tags",
"tags": ["verao"]
}
JSON,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"
)

func main() {
	req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/bulk", strings.NewReader(`{
"ids": ["8f3a2b1c-4d5e-6f70-8192-a3b4c5d6e7f8"],
"action": "add_tags",
"tags": ["verao"]
}`))
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "succeeded": 1,
    "failed": 1,
    "items": [
      {
        "id": "8f3a2b1c-4d5e-6f70-8192-a3b4c5d6e7f8",
        "ok": true
      },
      {
        "id": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
        "ok": false,
        "error_code": "dynamic_qr.not_found",
        "error": "QR code not found"
      }
    ]
  }
}

Criar QRs em lote

POST/v1/tools/image/qr-code/batches
Custo
Criação de cada linha válida, com desconto por volume
Dry-run
Não
Autenticação
qualquer um detools:qr:dynamic:managetools:manage

Até 1.000 linhas por lote, de um destes jeitos:

  • JSON com rows (uma lista de objetos com os campos do QR) ou csv (o texto do CSV), mais defaults com os valores usados nas linhas que não os trazem (folder_id, tags, scan_limit, expires_at, utm_forwarding, cores, custom_domain_id);
  • corpo text/csv direto, com folder_id e tags como parâmetros de query.

O CSV tem uma linha de cabeçalho e as colunas name, target_url (obrigatória), custom_slug, folder_id, tags (separadas por |), scan_limit, expires_at, utm_forwarding, foreground_color e background_color:

name,target_url,custom_slug,tags
Loja Centro,https://loja.example.com/centro,loja-centro,varejo|sp
Loja Norte,https://loja.example.com/norte,loja-norte,varejo
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/image/qr-code/batches?tags=campanha' \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'X-Idempotency-Key: 7f1c2e84-5b3a-4d6c-9e01-a2b3c4d5e6f7' \
  -H 'Content-Type: text/csv' \
  --data-binary @lojas.csv

As linhas inválidas são reportadas e não são cobradas; as válidas são cobradas de uma vez, com desconto por volume (a partir de 100, 500 e 1.000 linhas), e criadas em segundo plano (202). Uma linha que falhar depois é estornada. Acompanhe consultando o detalhe do lote até pending chegar a 0. Com o mesmo X-Idempotency-Key e as mesmas linhas, o retry devolve o mesmo lote, cobrado uma vez. Acima do limite, a resposta é 413 (dynamic_qr.batch_too_large).

curl -sS -X POST 'https://api.respawntech.dev/v1/tools/image/qr-code/batches' \
  -H "X-API-Token: $RESPAWN_API_TOKEN" \
  -H 'X-Idempotency-Key: 7f1c2e84-5b3a-4d6c-9e01-a2b3c4d5e6f7' \
  -H 'Content-Type: application/json' \
  -d '{
"csv": "name,target_url,custom_slug,tags\nLoja Centro,https://loja.example.com/centro,loja-centro,varejo|sp\nLoja Norte,https://loja.example.com/norte,loja-norte,varejo",
"defaults": { "utm_forwarding": true }
}'
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/batches', {
  method: 'POST',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
    'X-Idempotency-Key': '7f1c2e84-5b3a-4d6c-9e01-a2b3c4d5e6f7',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
"csv": "name,target_url,custom_slug,tags\nLoja Centro,https://loja.example.com/centro,loja-centro,varejo|sp\nLoja Norte,https://loja.example.com/norte,loja-norte,varejo",
"defaults": { "utm_forwarding": true }
}),
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.post(
    "https://api.respawntech.dev/v1/tools/image/qr-code/batches",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
        "X-Idempotency-Key": "7f1c2e84-5b3a-4d6c-9e01-a2b3c4d5e6f7",
    },
    json={
"csv": "name,target_url,custom_slug,tags\nLoja Centro,https://loja.example.com/centro,loja-centro,varejo|sp\nLoja Norte,https://loja.example.com/norte,loja-norte,varejo",
"defaults": { "utm_forwarding": True }
},
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/batches');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
        'X-Idempotency-Key: 7f1c2e84-5b3a-4d6c-9e01-a2b3c4d5e6f7',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => <<<'JSON'
{
"csv": "name,target_url,custom_slug,tags\nLoja Centro,https://loja.example.com/centro,loja-centro,varejo|sp\nLoja Norte,https://loja.example.com/norte,loja-norte,varejo",
"defaults": { "utm_forwarding": true }
}
JSON,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"
)

func main() {
	req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/image/qr-code/batches", strings.NewReader(`{
"csv": "name,target_url,custom_slug,tags\nLoja Centro,https://loja.example.com/centro,loja-centro,varejo|sp\nLoja Norte,https://loja.example.com/norte,loja-norte,varejo",
"defaults": { "utm_forwarding": true }
}`))
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
	req.Header.Set("X-Idempotency-Key", "7f1c2e84-5b3a-4d6c-9e01-a2b3c4d5e6f7")
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 202 — o lote é criado em segundo plano

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "id": "7d1e4a90-2c3b-4f58-a6e7-9b0c1d2e3f40",
    "status": "processing",
    "total_items": 2,
    "created": 0,
    "failed": 0,
    "pending": 2,
    "charged_units": 20000,
    "discount_units": 0,
    "refunded_units": 0,
    "created_at": "2026-09-20T14:00:00Z",
    "completed_at": null
  }
}

Listar lotes

GET/v1/tools/image/qr-code/batches
Custo
Franquia diária, depois 0,005 crédito/leitura via API
Dry-run
Suportado (X-Dry-Run: true)
Autenticação
qualquer um detools:qr:dynamic:readtools:qr:dynamic:managetools:readtools:manage
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/image/qr-code/batches' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/batches', {
  method: 'GET',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.get(
    "https://api.respawntech.dev/v1/tools/image/qr-code/batches",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/batches');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/image/qr-code/batches", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": [
    {
      "id": "7d1e4a90-2c3b-4f58-a6e7-9b0c1d2e3f40",
      "status": "completed",
      "total_items": 2,
      "created": 2,
      "failed": 0,
      "pending": 0,
      "charged_units": 20000,
      "discount_units": 0,
      "refunded_units": 0,
      "created_at": "2026-09-20T14:00:00Z",
      "completed_at": "2026-09-20T14:00:07Z"
    }
  ]
}

Detalhe do lote

GET/v1/tools/image/qr-code/batches/{id}
Custo
Franquia diária, depois 0,01 crédito/leitura via API
Dry-run
Suportado (X-Dry-Run: true)
Autenticação
qualquer um detools:qr:dynamic:readtools:qr:dynamic:managetools:readtools:manage

items traz cada linha (row, status, qr_id, slug, full_url ou error_code e error_message). charged_units, discount_units e refunded_units estão nas unidades internas de crédito (10.000 unidades = 1 crédito).

curl -sS -X GET 'https://api.respawntech.dev/v1/tools/image/qr-code/batches/550e8400-e29b-41d4-a716-446655440000' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/batches/550e8400-e29b-41d4-a716-446655440000', {
  method: 'GET',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.get(
    "https://api.respawntech.dev/v1/tools/image/qr-code/batches/550e8400-e29b-41d4-a716-446655440000",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/batches/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/image/qr-code/batches/550e8400-e29b-41d4-a716-446655440000", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "id": "7d1e4a90-2c3b-4f58-a6e7-9b0c1d2e3f40",
    "status": "completed",
    "total_items": 2,
    "created": 2,
    "failed": 0,
    "pending": 0,
    "charged_units": 20000,
    "discount_units": 0,
    "refunded_units": 0,
    "created_at": "2026-09-20T14:00:00Z",
    "completed_at": "2026-09-20T14:00:07Z",
    "items": [
      {
        "row": 1,
        "status": "created",
        "name": "Loja Centro",
        "target_url": "https://loja.example.com/centro",
        "cost_units": 10000,
        "qr_id": "8f3a2b1c-4d5e-6f70-8192-a3b4c5d6e7f8",
        "slug": "loja-centro",
        "full_url": "https://api.respawntech.dev/r/loja-centro"
      },
      {
        "row": 2,
        "status": "created",
        "name": "Loja Norte",
        "target_url": "https://loja.example.com/norte",
        "cost_units": 10000,
        "qr_id": "9e8d7c6b-5a49-4382-b1a0-f9e8d7c6b5a4",
        "slug": "loja-norte",
        "full_url": "https://api.respawntech.dev/r/loja-norte"
      }
    ]
  }
}

Exportar scans em CSV

GET/v1/tools/image/qr-code/dynamic/{id}/scans/export
Custo
Franquia diária, depois 0,01 crédito/leitura via API
Dry-run
Suportado (X-Dry-Run: true)
Autenticação
qualquer um detools:qr:dynamic:readtools:qr:dynamic:managetools:readtools:manage

Query: period (last_7d, last_30d padrão, last_90d ou all) ou from e to (YYYY-MM-DD, UTC, inclusive), que substituem o período. Cobrada como o histórico de scans. Salve com curl -o scans.csv.

curl -sS -X GET 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/scans/export?period=last_30d' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/scans/export?period=last_30d', {
  method: 'GET',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.get(
    "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/scans/export?period=last_30d",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/scans/export?period=last_30d');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/scans/export?period=last_30d", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200 — text/csv, com Content-Disposition: attachment. Colunas: scanned_at, country, city, os, browser, device, referer, user_agent, rule_id, variant (mais antigos primeiro)

Apagar o histórico de scans

DELETE/v1/tools/image/qr-code/dynamic/{id}/scans
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools:qr:dynamic:managetools:manage

Apaga as linhas de scan (todas, ou as anteriores a before, YYYY-MM-DD UTC). total_scans e scan_limit são mantidos. É irreversível.

curl -sS -X DELETE 'https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/scans?before=2026-01-01' \
  -H "X-API-Token: $RESPAWN_API_TOKEN"
const response = await fetch('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/scans?before=2026-01-01', {
  method: 'DELETE',
  headers: {
    'X-API-Token': process.env.RESPAWN_API_TOKEN,
  },
});

if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());
import os
import requests

response = requests.delete(
    "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/scans?before=2026-01-01",
    headers={
        "X-API-Token": os.environ["RESPAWN_API_TOKEN"],
    },
    timeout=30,
)
if not response.ok:
    raise RuntimeError(response.json()["error"]["code"])
print(response.json())
<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/scans?before=2026-01-01');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_HTTPHEADER => [
        'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest("DELETE", "https://api.respawntech.dev/v1/tools/image/qr-code/dynamic/550e8400-e29b-41d4-a716-446655440000/scans?before=2026-01-01", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	data, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	if res.StatusCode >= 400 {
		panic(string(data))
	}
	fmt.Println(string(data))
}

Resposta 200

{
  "success": true,
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "deleted": 42
  }
}

Erros

CódigoHTTPQuando
dynamic_qr.name_required400Campo name obrigatório.
dynamic_qr.invalid_url400URL de destino inválida.
dynamic_qr.invalid_slug400Slug fora do padrão [a-zA-Z0-9-]{3,50}.
dynamic_qr.malicious_url403URL bloqueada pela verificação de segurança.
dynamic_qr.not_found404QR inexistente ou de outra conta.
dynamic_qr.slug_taken409Slug já em uso.
dynamic_qr.invalid_rules400Regra de redirect inválida: sem condição, id repetido, valor de condição desconhecido ou mais de 20 regras.
dynamic_qr.invalid_variants400Teste A/B inválido: de 2 a 10 variantes, id único, weight de 1 a 1000 e target_url absoluta.
dynamic_qr.invalid_pixels400ID de pixel fora do formato (GA4 G-XXXX, Meta só dígitos, GTM GTM-XXXX).
dynamic_qr.invalid_fallback_urls400expired_url e limit_reached_url precisam ser URLs absolutas.
dynamic_qr.invalid_content400Conteúdo hospedado inválido para o type informado.
dynamic_qr.folder_not_found404Pasta inexistente ou de outra conta.
dynamic_qr.folder_taken409Já existe uma pasta com esse nome.
dynamic_qr.folder_invalid400O nome da pasta tem de 1 a 80 caracteres.
dynamic_qr.tags_invalid400Tags inválidas: até 20 por QR, até 40 caracteres cada e sem vírgula ou ponto e vírgula.
dynamic_qr.bulk_invalid400Edição em massa inválida: action desconhecida, de 1 a 500 ids ou campo exigido pela ação ausente.
dynamic_qr.batch_not_found404Lote inexistente ou de outra conta.
dynamic_qr.batch_invalid400Lote sem linhas ou CSV malformado (sem cabeçalho, coluna desconhecida ou sem target_url).
dynamic_qr.batch_too_large413Mais de 1.000 linhas no lote.
dynamic_qr.scan_range_invalid400from/to/before fora do formato YYYY-MM-DD.
qr_domain.invalid_hostname400O hostname precisa ser um subdomínio público, como qr.suaempresa.com (sem porta, caminho, IP ou curinga).
qr_domain.not_found404Domínio inexistente ou de outra conta.
qr_domain.taken409Esse hostname já está cadastrado.
qr_domain.in_use409Há QRs usando o domínio; exclua-os ou mova-os antes de remover.
qr_domain.not_usable409O domínio falhou ou está suspenso e não aceita novos QRs.
qr_domain.plan_limit403O plano não permite mais domínios próprios.
qr_domain.unavailable503Domínios próprios indisponíveis no momento.
qr_domain.provider_failed502O provedor de certificados recusou ou não respondeu; tente de novo.
dynamic_qr.generation_failed502Falha ao gerar o QR.
dynamic_qr.image_generation_failed502Falha ao renderizar a imagem do QR.

Peculiaridades e limites

  • URLs maliciosas são bloqueadas automaticamente (dynamic_qr.malicious_url).
  • Proteção anti-loop: o destino não pode apontar para o próprio domínio de redirect.
  • O primeiro QR não ocupa slot pago; os demais ocupam slots em grupos de 3.
  • skip_image_generation: true reduz o custo quando você gera a imagem do QR por conta própria.
  • Slug: 3–50 caracteres [a-zA-Z0-9-]. Listagens: até 100 itens por página.

Atualizado em