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:
POSTcomX-Dry-Run: truepara validar o payload e ver o custo.POSTdefinitivo comX-Idempotency-Key→ recebe a imagem do QR, oresource_ide oslug.- Use a imagem retornada (ou gere a sua apontando para
full_url, disponível emGET /{id}). - 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ção | Rota |
|---|---|
| Criar QR dinâmico | POST/ |
| Listar QRs | GET/ |
| Detalhe | GET/{id} |
| Atualizar destino, nome ou design | PATCH/{id} |
| Reativar | PUT/{id}/activate |
| Desativar | PUT/{id}/deactivate |
| Excluir | DELETE/{id} |
| Verificar disponibilidade de slug | GET/check-slug/{slug} |
| Overview de analytics | GET/{id}/analytics |
| Scans por período | GET/{id}/analytics/period |
| Distribuição por dispositivo | GET/{id}/analytics/device |
| Histórico de scans | GET/{id}/scans |
| Exportar scans em CSV | GET/{id}/scans/export |
| Apagar o histórico de scans | DELETE/{id}/scans |
| Editar vários QRs de uma vez | POST/bulk |
| Listar pastas | GET/v1/tools/image/qr-code/folders |
| Criar pasta | POST/v1/tools/image/qr-code/folders |
| Renomear pasta | PATCH/v1/tools/image/qr-code/folders/{id} |
| Excluir pasta | DELETE/v1/tools/image/qr-code/folders/{id} |
| Criar QRs em lote | POST/v1/tools/image/qr-code/batches |
| Listar lotes | GET/v1/tools/image/qr-code/batches |
| Detalhe do lote | GET/v1/tools/image/qr-code/batches/{id} |
| Listar domínios próprios | GET/v1/tools/image/qr-code/domains |
| Detalhe do domínio | GET/v1/tools/image/qr-code/domains/{id} |
| Cadastrar domínio próprio | POST/v1/tools/image/qr-code/domains |
| Editar páginas de fallback do domínio | PATCH/v1/tools/image/qr-code/domains/{id} |
| Verificar o domínio agora | POST/v1/tools/image/qr-code/domains/{id}/refresh |
| Remover domínio | DELETE/v1/tools/image/qr-code/domains/{id} |
| Redirect público | GET/r/{short_id} |
Custos
Criação — cobrada uma vez no POST (opções ligadas depois num PATCH custam o mesmo)
| Item | Créditos | Notas |
|---|---|---|
| Base (com imagem) | 1,0 | — |
| Base sem gerar imagem | 0,5 | skip_image_generation: true |
| Slug customizado | +35,0 | 3–50 caracteres [a-zA-Z0-9-] |
| Gradiente no design | +5,0 | — |
| Olhos customizados | +2,0 | eye_style ou eye_dot_style shield/flower |
| Controle de expiração | +2,0 | expires_at |
| Limite de scans | +2,0 | scan_limit |
| Processamento de logo | +4,0 | — |
| Encaminhamento UTM | +59,0 | Repassa a query string ao destino |
| Regras de redirect | +99,0 | redirect_rules |
| Teste A/B | +79,0 | ab_variants |
| Senha | +49,0 | password |
| Pixels de rastreamento | +59,0 | pixels (GA4, Meta, GTM) |
| Redirect de expirado/esgotado | +29,0 | expired_url ou limit_reached_url |
| Página hospedada | +49,0 | content (vCard, arquivo, links, cardápio, Wi-Fi, loja de apps) |
| Render PNG extra | +1,5 | formato png ou pdf (cobrado uma vez) |
Domínio próprio
| Item | Créditos | Notas |
|---|---|---|
| Domínio ativo (por 30 dias) | 99,0 | Cobrado ao ativar e a cada 30 dias; domínio pendente, com falha ou suspenso não é cobrado |
Lote
| Item | Créditos | Notas |
|---|---|---|
| Linha válida do lote | 1,0 | Desconto 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.
| Item | Créditos/scan |
|---|---|
| 0–1 slots pagos | 0,05 |
| 2–20 slots pagos | 0,045 |
| 21–40 slots pagos | 0,04 |
| 41–80 slots pagos | 0,035 |
| 81+ slots pagos | 0,025 |
Armazenamento
| Item | Créditos | Notas |
|---|---|---|
| Primeiro QR dinâmico | 0,0 | Não ocupa slot pago |
| Slot (grupo de 3 QRs) | 3,0 / dia | QRs 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
/v1/tools/image/qr-code/dynamic- Custo
- Variável (ver custos)
- Dry-run
- Suportado (
X-Dry-Run: true) - Autenticação
- qualquer um de
tools: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
/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 de
tools: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
/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 de
tools: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
/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 de
tools: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
/v1/tools/image/qr-code/dynamic/{id}/activate- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools: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
/v1/tools/image/qr-code/dynamic/{id}/deactivate- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools: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
/v1/tools/image/qr-code/dynamic/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools: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
/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 de
tools: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
/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 de
tools: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
/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 de
tools: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
/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 de
tools: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
/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 de
tools: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
/r/{short_id}- Custo
- Por scan (ver custos)
- Dry-run
- Não
- Autenticação
- Pública (sem token)
- Ativo:
307para atarget_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.
| Tipo | Nome | Valor |
|---|---|---|
CNAME | qr (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
/v1/tools/image/qr-code/domains- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools: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
/v1/tools/image/qr-code/domains/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools: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
/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 de
tools: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
/v1/tools/image/qr-code/domains/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools: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
/v1/tools/image/qr-code/domains/{id}/refresh- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools: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
/v1/tools/image/qr-code/domains/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools: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.
| Campo | Significado |
|---|---|
id | Nome da regra ([a-zA-Z0-9_-], até 40); aparece nos scans e em rule_stats do analytics |
target_url | Destino quando a regra casa (URL absoluta) |
countries | Países ISO 3166-1 alfa-2 em maiúsculas (["BR", "PT"]) |
devices | mobile, tablet, desktop |
os | ios, android, windows, macos, linux, other |
languages | Idioma preferido do visitante: pt casa pt e pt-br; pt-br só pt-br |
days | Dias da semana, 0 (domingo) a 6 (sábado), no timezone |
start_time, end_time | Janela do dia (HH:MM); se o fim vier antes do início, atravessa a meia-noite. Os dois ou nenhum |
timezone | Fuso IANA (America/Sao_Paulo); UTC se vazio |
from, until | Perí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.
type | Campo | O scan mostra |
|---|---|---|
vcard | vcard (FirstName, LastName, Org, Title, Phone, Email, Website, Address) | Página de contato e download do .vcf (?format=vcf). Exige nome ou organização |
file | file (url, name) | Abre o arquivo ou PDF hospedado (URL absoluta) |
links | links (title, description, avatar_url, links[] com label e url) | Link na bio, de 1 a 50 links |
menu | menu (title, currency, sections[] → items[] com name, description, price) | Cardápio, até 30 seções e 300 itens |
wifi | wifi (SSID, Password, Security, Hidden) | Nome da rede e senha para copiar |
app_store | app_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) eposition(topoubottom).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. Semdpi, 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
/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 de
tools: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
/v1/tools/image/qr-code/folders- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools: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
/v1/tools/image/qr-code/folders/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools: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
/v1/tools/image/qr-code/folders/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools: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
/v1/tools/image/qr-code/dynamic/bulk- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools: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
/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 de
tools: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) oucsv(o texto do CSV), maisdefaultscom 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/csvdireto, comfolder_idetagscomo 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,varejocurl -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.csvAs 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
/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 de
tools: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
/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 de
tools: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
/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 de
tools: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
/v1/tools/image/qr-code/dynamic/{id}/scans- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools: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ódigo | HTTP | Quando |
|---|---|---|
dynamic_qr.name_required | 400 | Campo name obrigatório. |
dynamic_qr.invalid_url | 400 | URL de destino inválida. |
dynamic_qr.invalid_slug | 400 | Slug fora do padrão [a-zA-Z0-9-]{3,50}. |
dynamic_qr.malicious_url | 403 | URL bloqueada pela verificação de segurança. |
dynamic_qr.not_found | 404 | QR inexistente ou de outra conta. |
dynamic_qr.slug_taken | 409 | Slug já em uso. |
dynamic_qr.invalid_rules | 400 | Regra de redirect inválida: sem condição, id repetido, valor de condição desconhecido ou mais de 20 regras. |
dynamic_qr.invalid_variants | 400 | Teste A/B inválido: de 2 a 10 variantes, id único, weight de 1 a 1000 e target_url absoluta. |
dynamic_qr.invalid_pixels | 400 | ID de pixel fora do formato (GA4 G-XXXX, Meta só dígitos, GTM GTM-XXXX). |
dynamic_qr.invalid_fallback_urls | 400 | expired_url e limit_reached_url precisam ser URLs absolutas. |
dynamic_qr.invalid_content | 400 | Conteúdo hospedado inválido para o type informado. |
dynamic_qr.folder_not_found | 404 | Pasta inexistente ou de outra conta. |
dynamic_qr.folder_taken | 409 | Já existe uma pasta com esse nome. |
dynamic_qr.folder_invalid | 400 | O nome da pasta tem de 1 a 80 caracteres. |
dynamic_qr.tags_invalid | 400 | Tags inválidas: até 20 por QR, até 40 caracteres cada e sem vírgula ou ponto e vírgula. |
dynamic_qr.bulk_invalid | 400 | Edição em massa inválida: action desconhecida, de 1 a 500 ids ou campo exigido pela ação ausente. |
dynamic_qr.batch_not_found | 404 | Lote inexistente ou de outra conta. |
dynamic_qr.batch_invalid | 400 | Lote sem linhas ou CSV malformado (sem cabeçalho, coluna desconhecida ou sem target_url). |
dynamic_qr.batch_too_large | 413 | Mais de 1.000 linhas no lote. |
dynamic_qr.scan_range_invalid | 400 | from/to/before fora do formato YYYY-MM-DD. |
qr_domain.invalid_hostname | 400 | O hostname precisa ser um subdomínio público, como qr.suaempresa.com (sem porta, caminho, IP ou curinga). |
qr_domain.not_found | 404 | Domínio inexistente ou de outra conta. |
qr_domain.taken | 409 | Esse hostname já está cadastrado. |
qr_domain.in_use | 409 | Há QRs usando o domínio; exclua-os ou mova-os antes de remover. |
qr_domain.not_usable | 409 | O domínio falhou ou está suspenso e não aceita novos QRs. |
qr_domain.plan_limit | 403 | O plano não permite mais domínios próprios. |
qr_domain.unavailable | 503 | Domínios próprios indisponíveis no momento. |
qr_domain.provider_failed | 502 | O provedor de certificados recusou ou não respondeu; tente de novo. |
dynamic_qr.generation_failed | 502 | Falha ao gerar o QR. |
dynamic_qr.image_generation_failed | 502 | Falha 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: truereduz 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