Crie seu primeiro link curto com a API da CodeQR
Crie, leia, atualize e liste links curtos com a API REST da CodeQR — requisições e respostas reais em curl, limites por plano, externalId e o SDK.

Ao fim deste guia você terá criado um link curto pela linha de comando, lido o link de volta, trocado o destino, listado seus links com paginação e saberá os limites que o seu plano aplica. Toda requisição abaixo rodou num workspace real em 2026-08-17; só o domínio foi trocado por go.example.com.
Disponibilidade
- Plano: todos os planos. Requisições por minuto por chave — Free 60, Starter 100, Pro 500, Business 1.000, Scale 10.000.
- Onde: URL base
https://api.codeqr.io, chave em Configurações → Chaves de API. Referência de endpoints em docs.codeqr.io; QR codes têm guia próprio, Crie QR Codes com a API, o MCP e automações.
Antes de começar
- Uma chave de API com Links → Write (ou Acesso Total). Veja Crie uma chave de API e escolha as permissões.
- A chave decide o workspace. Não há ID de workspace para enviar;
projectSlugouprojectIdna query string são ignorados quando você autentica com uma chave. - Envie JSON com
Content-Type: application/json. As respostas são JSON; erros vêm como{ "error": { "code", "message", "doc_url" } }. - Atualizações usam
PUT, nãoPATCH(PATCH /links/{id}responde405).
Passos
- Crie um link.
domainekeysão opcionais: sem eles o link sai no domínio padrão do workspace com uma chave aleatória de 7 caracteres.
curl -X POST https://api.codeqr.io/links \
-H "Authorization: Bearer codeqr_••••••••••••••••••••••••" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/summer-sale?utm_source=newsletter&utm_medium=email&utm_campaign=summer","domain":"go.example.com","key":"docs-summer","comments":"Created from the help-center test"}'Resposta 200 (recortada aos campos que você vai usar; o objeto completo está na referência):
{
"id": "cmsxh367q0003wu8tzav2adsp",
"domain": "go.example.com",
"key": "docs-summer",
"url": "https://example.com/summer-sale?utm_source=newsletter&utm_medium=email&utm_campaign=summer",
"shortLink": "https://go.example.com/docs-summer",
"qrCode": "https://api.codeqr.io/qr?url=https://go.example.com/docs-summer?qr=1",
"archived": false,
"expiresAt": null,
"externalId": null,
"utm_source": "newsletter",
"utm_medium": "email",
"utm_campaign": "summer",
"tags": [],
"comments": "Created from the help-center test",
"clicks": 0,
"createdAt": "2026-08-17T16:53:20.822Z"
}Guarde o id — é o que toda outra chamada usa. Os utm_* são lidos da URL de destino; enviá-los como campos separados não faz nada.
- Leia de volta por domínio e chave, ou por id:
curl "https://api.codeqr.io/links/info?domain=go.example.com&key=docs-summer" \ -H "Authorization: Bearer codeqr_••••••••••••••••••••••••" curl https://api.codeqr.io/links/cmsxh367q0003wu8tzav2adsp \ -H "Authorization: Bearer codeqr_••••••••••••••••••••••••"
As duas devolvem o mesmo objeto de link; clicks cresce conforme as pessoas abrem o link curto.
- Troque o destino com
PUTe só os campos que quer mudar:
curl -X PUT https://api.codeqr.io/links/cmsxh367q0003wu8tzav2adsp \
-H "Authorization: Bearer codeqr_••••••••••••••••••••••••" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/summer-sale-v2","comments":"updated via PUT"}'Resposta 200 com "url": "https://example.com/summer-sale-v2" e um novo updatedAt. O link curto https://go.example.com/docs-summer continua funcionando e agora redireciona para a nova URL. Se a nova URL não trouxer parâmetros UTM, os utm_* anteriores ficam no link até uma URL com novos parâmetros ser definida.
- Liste e pesquise.
pagecomeça em 1;pageSizevai até 100 (padrão 100);sortByaceitacreatedAt,clicks,lastClicked.
curl "https://api.codeqr.io/links?search=docs-&page=1&pageSize=2&sortBy=createdAt" \ -H "Authorization: Bearer codeqr_••••••••••••••••••••••••"
Devolve um array de objetos de link (cada um traz também user e tags). Para contar em vez de listar: GET /links/count (acrescente tagIds=<id> para contar uma tag).
- Torne a criação idempotente com
externalId. Envie o id do seu próprio registro na criação; consulte depois com o prefixoext_; uma segunda criação com o mesmoexternalIdé recusada em vez de duplicada:
curl -X POST https://api.codeqr.io/links \
-H "Authorization: Bearer codeqr_••••••••••••••••••••••••" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/products/42","domain":"go.example.com","externalId":"row-42"}'
# → 200, "key": "3FSQv8g", "externalId": "row-42"
curl "https://api.codeqr.io/links/info?externalId=ext_row-42" \
-H "Authorization: Bearer codeqr_••••••••••••••••••••••••"
# → 200, o mesmo link
# o mesmo externalId de novo → 409 {"error":{"code":"conflict","message":"A link with this externalId already exists."}}- Apague com
DELETE /links/{id}→200 {"id": "…"}. Um link curto apagado para de redirecionar; os cliques passados ficam nos totais do workspace.
Limites de requisições e cabeçalhos
Toda resposta autenticada traz x-ratelimit-limit, x-ratelimit-remaining e x-ratelimit-reset (um timestamp Unix em milissegundos). Acima do limite a API responde 429 com Too many requests.; espere o próximo minuto. O cabeçalho retry-after nessas respostas traz o mesmo timestamp, não uma quantidade de segundos — não durma pelo valor dele.
Use o SDK em vez do curl
O SDK em TypeScript envolve os mesmos endpoints:
import Codeqr from '@codeqr/ts';
const client = new Codeqr({ apiKey: process.env['CODEQR_API_KEY'] });
const link = await client.links.create({ url: 'https://example.com/summer-sale', domain: 'go.example.com' });
console.log(link.shortLink);Não há SDK oficial em Python, Go ou PHP; use a API HTTP diretamente nessas linguagens.
Verifique se funcionou
- Abra
https://go.example.com/docs-summer(o seu link curto) num navegador — ele redireciona para o destino que você definiu. - Dois a três minutos depois,
GET /links/info?domain=…&key=…mostra"clicks": 1, e o link aparece em Links no painel com a mesma contagem. - Em Configurações → Chaves de API, a coluna Último uso da chave mostra um horário recente.
Solução de problemas
PUT funciona, mas PATCH devolve 405
Só PUT atualiza um link. Troque o verbo; o corpo é o mesmo JSON parcial.
tagNames devolve 400 com um erro longo de banco de dados
Enviar tagNames no POST /links falha hoje mesmo quando a tag existe (o texto do erro começa com Invalid prisma.link.create() invocation). Envie tagIds — pegue os ids em GET /tags — ou anexe a tag depois com PUT /links/{id} e tagIds. tagNames com um nome inexistente responde Invalid tagNames detected: <nome>.
GET /links/count?search=… devolve 500
O endpoint de contagem falha combinado com search. Conte sem search, filtre por tagIds, ou liste com pageSize e conte do seu lado.
A resposta não tem workspaceId e meu código esperava um
Links trazem projectId — esse é o id do workspace. Chaves são presas a um workspace, então você nunca o envia.
Recebo 401 "Unauthorized: Login required." mesmo enviando a chave
O cabeçalho Authorization não chegou à API — em geral o nome do cabeçalho foi digitado errado ou um proxy o removeu. Se você enviou o cabeçalho sem o prefixo Bearer , a resposta é um texto puro Authorization header misconfigured. Did you forget to add 'Bearer '?. Veja Corrija erros da API da CodeQR: 401, 403, 404, 409 e 429.