Uma API de encurtador de URLs é uma das integrações menores no backlog de uma equipa de engenharia típica. Três endpoints, um cabeçalho de autenticação, um payload JSON. A página de documentação promete a primeira chamada em cinco minutos. Então o tráfego de produção chega, a lógica de repetição cria links duplicados, o dashboard fica cheio de variantes /foo-1, /foo-2, /foo-3 do mesmo destino, e alguém abre um ticket.
Este post percorre a integração real. Autenticação, a primeira chamada, os quatro endpoints que cobrem a maioria dos casos de uso, idempotência, tratamento de erros, limites de taxa e as armadilhas de produção que o guia rápido de cinco minutos ignora. Exemplos de código em TypeScript, Python, Go, Ruby e PHP — os três primeiros através dos SDKs oficiais (@elido/sdk, elido-python, github.com/elido/elido-go), os dois últimos através de clientes HTTP comuns.
Pré-requisitos#
Faça login no dashboard, navegue até /settings/api e crie um token de acesso pessoal. Os tokens têm escopo de workspace — um token emitido no workspace A não pode criar links no workspace B. Tokens de conta de serviço (para sistemas de CI, ferramentas internas, integração máquina a máquina) são criados na mesma tela nos planos Pro e superiores; eles têm escopos explícitos (links:write, analytics:read, domains:write) e rotacionam independentemente dos tokens pessoais.
A URL base é https://api.elido.app/v1. Os domínios de redirecionamento (f.elido.me, s.elido.me, b.elido.me) são separados da superfície da API. Seus links curtos resolvem no domínio de redirecionamento; a API serve para criá-los, modificá-los e lê-los.
A especificação OpenAPI é publicada em https://api.elido.app/v1/openapi.json e está em conformidade com OpenAPI 3.1. Os SDKs oficiais são gerados a partir dessa especificação e republicados a cada lançamento da API; você também pode gerar seu próprio cliente em qualquer linguagem compatível com OpenAPI.
A primeira chamada#
Crie um link curto a partir da URL de destino. Cinco linhas em TypeScript:
import { Elido } from "@elido/sdk";
const elido = new Elido({ token: process.env.ELIDO_TOKEN! });
const link = await elido.links.create({
destinationUrl: "https://shop.example.com/spring-sale",
});
console.log(link.shortUrl); // https://s.elido.me/abc123
Python:
from elido import Elido
client = Elido(token=os.environ["ELIDO_TOKEN"])
link = client.links.create(
destination_url="https://shop.example.com/spring-sale",
)
print(link.short_url) # https://s.elido.me/abc123
Go:
import "github.com/elido/elido-go/v2/elido"
client := elido.NewClient(elido.WithToken(os.Getenv("ELIDO_TOKEN")))
link, err := client.Links.Create(ctx, &elido.LinkCreateInput{
DestinationURL: "https://shop.example.com/spring-sale",
})
if err != nil {
return fmt.Errorf("create link: %w", err)
}
fmt.Println(link.ShortURL)
Ruby (sem SDK oficial — utilizando net/http):
require "net/http"
require "json"
uri = URI("https://api.elido.app/v1/links")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV['ELIDO_TOKEN']}"
req["Content-Type"] = "application/json"
req.body = { destination_url: "https://shop.example.com/spring-sale" }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
link = JSON.parse(res.body)
puts link["short_url"]
PHP (Guzzle):
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.elido.app/v1/']);
$res = $client->post('links', [
'headers' => ['Authorization' => 'Bearer ' . getenv('ELIDO_TOKEN')],
'json' => ['destination_url' => 'https://shop.example.com/spring-sale'],
]);
$link = json_decode((string) $res->getBody(), true);
echo $link['short_url'];
Todos os cinco produzem o mesmo resultado. O corpo da resposta contém a URL curta, o ID canônico do link, o ID do workspace e o timestamp de criação. O slug — abc123 no exemplo acima — é gerado pelo servidor, a menos que você passe custom_slug na requisição. O alfabeto do slug é base62 ([0-9A-Za-z]); o comprimento padrão é de seis caracteres.
Os quatro endpoints que usará realmente#
A API tem mais de quatro endpoints, mas a maioria das integrações permanece dentro deste conjunto.
Criar um link#
POST /v1/links aceita a URL de destino e campos opcionais:
custom_slug— um slug à sua escolha (deve ser único dentro do workspace).domain_id— para links de domínio personalizado; o domínio principal do workspace é usado se omitido.tags— um array de strings de formato livre para organização.utm— parâmetros de campanha para anexar ao destino no momento do redirecionamento.expires_at— timestamp ISO 8601 após o qual o link retorna 410 Gone.password— se definido, o redirecionamento serve uma página de password antes de reencaminhar.metadata— objeto JSON opaco que o redirecionamento não interpreta; útil para as suas próprias chaves de junção.
O slug personalizado é o campo que costuma causar problemas às equipas em produção. Se passar um slug que já está em uso por outro link no mesmo workspace, a API retorna 409 Conflict. O tratador de repetição ingénuo que anexa um contador (my-slug-1, my-slug-2) produz o problema de links duplicados descrito no início. O comportamento de repetição correto é descrito na secção de idempotência abaixo.
Ler um link#
GET /v1/links/{id} retorna o registo completo do link, incluindo a contagem atual de cliques, o timestamp do clique mais recente e toda a configuração. O ID do link é o identificador canônico — os slugs podem mudar (o plano Pro+ suporta renomeação de slugs), os IDs não.
GET /v1/links?domain_id=…&tag=…&limit=… lista links no workspace com filtros. A paginação é baseada em cursor; o next_cursor na resposta é opaco e volta como o parâmetro de consulta cursor na próxima requisição.
Atualizar um link#
PATCH /v1/links/{id} aceita os mesmos campos que a criação. As atualizações mais comuns: alterar a URL de destino (útil para rotação de campanha sem reimprimir QR codes), alterar tags, estender expires_at. Atualizar o slug é um endpoint separado POST /v1/links/{id}/rename que lida com o redirecionamento 301 do slug antigo por um período de retenção configurável (padrão de 30 dias).
Eliminar um link#
DELETE /v1/links/{id} realiza uma exclusão lógica (soft-delete). O link retorna 410 Gone pelos próximos 90 dias e, em seguida, é eliminado permanentemente. A visualização da lixeira do dashboard mostra links excluídos logicamente; pode restaurar via dashboard ou via POST /v1/links/{id}/restore dentro da janela de 90 dias.
Chaves de idempotência#
Toda requisição de mutação — POST, PATCH, DELETE — aceita um cabeçalho Idempotency-Key. O valor do cabeçalho é uma string opaca de até 255 caracteres; o servidor armazena o corpo da resposta e o código de status por 24 horas indexados por (workspace_id, idempotency_key) e retorna a resposta armazenada se a mesma chave for apresentada novamente.
Os SDKs oficiais geram chaves de idempotência automaticamente quando não são fornecidas. Pode substituir:
const link = await elido.links.create(
{ destinationUrl: "https://shop.example.com/spring-sale" },
{ idempotencyKey: "order-12345-link" },
);
O caso de uso é um loop de repetição. Se o seu job cria um link como parte do processamento de um pedido upstream, gere a chave de idempotência a partir do ID do pedido. Uma repetição do mesmo job vê a mesma chave, atinge o cache de idempotência e retorna o link criado originalmente, em vez de produzir um segundo link.
A principal armadilha: o cache de idempotência dura 24 horas, não para sempre. Uma repetição no terceiro dia de um job travado criará um novo link. Se a integração for executada em lotes de vários dias, armazene o ID do link retornado pela primeira criação bem-sucedida e consulte-o antes de emitir novamente.
Uma segunda armadilha: a idempotência é por workspace. A mesma chave em dois workspaces cria dois links. Esta é a semântica correta para uma API multi-workspace, mas pode surpreender equipas que assumem que a chave é globalmente única.
Tratamento de erros#
A API retorna códigos de status HTTP padrão, além de um corpo de erro estruturado:
{
"error": {
"code": "rate_limit_exceeded",
"message": "Workspace rate limit of 100 req/s exceeded. Retry after 1 second.",
"request_id": "req_01HXYZAB123",
"retry_after": 1
}
}
Os códigos que verá com mais frequência:
400 invalid_request— falha na validação do payload. O campomessagelista os campos específicos. Não repita a chamada; corrija o payload.401 unauthorized— token ausente ou inválido. Não repita sem rotacionar o token.403 forbidden— o token não tem o escopo necessário. Verifique a lista de escopos do token em/settings/api.404 not_found— o recurso não existe ou o token não tem acesso a ele (retornamos 404 em vez de 403 para evitar fugas de informação sobre a existência de recursos a chamadores não autorizados).409 conflict— slug já em uso, ou edição simultânea detetada (PATCH numa versão antiga). Procure novamente e tente de novo.429 rate_limit_exceeded— aguarde de acordo com o valor deretry_after.500 internal_server_error— falha no lado do servidor. Seguro repetir com a mesma chave de idempotência.502 bad_gateway,503 service_unavailable,504 gateway_timeout— problemas transitórios de infraestrutura. Aguarde e tente novamente.
Os SDKs oficiais implementam backoff exponencial com jitter para 429, 500, 502, 503 e 504. Eles não repetem 400, 401, 403, 404 ou 409 — esses são erros de programação ou conflitos de lógica de negócio, não falhas transitórias. Clientes HTTP personalizados devem seguir o mesmo padrão; repetir um 400 com o mesmo payload não produzirá um resultado diferente.
O request_id no corpo do erro é o campo a ser incluído nos tickets de suporte. Podemos rastrear qualquer requisição a partir desse ID através do log de auditoria, do log da aplicação e das métricas da plataforma — e não podemos rastrear uma requisição sem ele.
Limites de taxa#
Os limites de taxa publicados são 100 requisições por segundo por workspace no plano Pro, 500 no Business e um limite negociado no Enterprise. O nível gratuito é de 10 req/s.
O estado do limite de taxa é exposto em três cabeçalhos de resposta em cada resposta da API:
X-RateLimit-Limit— o limite atual por segundo.X-RateLimit-Remaining— requisições restantes no segundo atual.X-RateLimit-Reset— timestamp Unix de quando o balde (bucket) faz reset.
O limite de 100/s é uma implementação de token-bucket com uma capacidade de burst de 200 — o que significa que pode emitir 200 requisições de uma vez se o bucket estiver cheio e, em seguida, estabilizar na taxa sustentada de 100/s. A maioria dos jobs de criação de links curtos cabe confortavelmente no burst; integrações ricas em análise que paginam eventos históricos de cliques beneficiam da margem do plano Pro.
Para operações em massa no Business+, o endpoint POST /v1/links/bulk aceita até 1000 links por requisição e conta como uma unidade de limite de taxa. Este é o endpoint correto para qualquer job que cria mais de cem links de uma vez.
O que os SDKs fazem que o HTTP comum não faz#
Os SDKs oficiais oferecem quatro coisas que se pagam rapidamente:
- Repetição automática com backoff para os códigos de status repetíveis.
- Geração de chave de idempotência quando não fornecida explicitamente.
- Erros tipados para que possa fazer
catch (err) { if (err instanceof ElidoRateLimitError) { … } }em vez de analisar JSON em blocos catch. - Iteradores de paginação para que os endpoints de listagem exponham iteradores assíncronos ou geradores em vez de exigir tratamento manual de cursores.
O SDK de Go expõe adicionalmente o cliente HTTP subjacente para instrumentação — útil se quiser ligá-lo à sua configuração de rastreamento existente. A página de recursos de API + SDKs do repositório cobre toda a superfície; a referência da API é publicada em /docs/api-reference.
Acesso a análises#
Os endpoints de análise são somente leitura e residem em /v1/workspaces/{id}/analytics/. As consultas mais comuns:
GET .../links/{id}/clicks?from=…&to=…— eventos de cliques brutos com paginação. Útil para pipelines de exportação.GET .../timeseries?from=…&to=…&bucket=day— contagem de cliques agrupados por dia para um intervalo de tempo.GET .../breakdown/country?from=…&to=…— detalhamento geográfico.GET .../breakdown/referrer?from=…&to=…— detalhamento por referenciador (referrer).
O feed de eventos de cliques brutos é o maior. Um workspace com 10M de cliques por mês produz cerca de 600MB de JSON por mês de dados de eventos brutos. Para exportações nesta escala, o guia de exportação para ClickHouse cobre o mecanismo de exportação em massa que ignora o envelope JSON e faz o streaming diretamente do warehouse de análise.
Webhooks para eventos de clique#
Webhooks são o inverso do polling — em vez de perguntar à API por novos cliques, a API entrega-os ao seu endpoint. Configure em /settings/webhooks:
await elido.webhooks.create({
url: "https://your-app.example/webhooks/elido",
events: ["link.click", "link.created", "link.expired"],
secret: process.env.WEBHOOK_SIGNING_SECRET,
});
Cada entrega inclui um cabeçalho Elido-Signature contendo um HMAC-SHA256 do corpo da requisição com o seu segredo compartilhado. Verifique a assinatura antes de processar — sem ela, qualquer chamador pode postar no seu endpoint de webhook e fazer-se passar pela Elido.
A semântica de entrega é pelo menos uma vez (at-least-once) com backoff exponencial até uma retenção máxima de 72 horas. Para o formato detalhado e o comportamento de repetição, o post webhooks vs polling para rastreamento de cliques compara os dois padrões de integração.
Um exemplo prático: automação de campanha#
A integração que motiva a maior parte da adoção da API assemelha-se a isto. A sua automação de marketing cria uma campanha no Customer.io ou HubSpot. Um hook é acionado quando a campanha é publicada. O seu handler cria o link curto, anexa-o ao registo da campanha e envia-o de volta para a ferramenta de gestão de campanha para substituir no template de e-mail.
Em TypeScript:
import { Elido } from "@elido/sdk";
const elido = new Elido({ token: process.env.ELIDO_TOKEN! });
export async function onCampaignPublished(campaign: Campaign) {
const link = await elido.links.create(
{
destinationUrl: campaign.destinationUrl,
tags: ["campaign", `campaign:${campaign.id}`, campaign.channel],
utm: {
source: campaign.channel,
medium: "email",
campaign: campaign.slug,
},
metadata: { campaign_id: campaign.id, batch: campaign.batchId },
},
{
idempotencyKey: `campaign-${campaign.id}-link`,
},
);
await campaignStore.update(campaign.id, { shortUrl: link.shortUrl });
return link;
}
A chave de idempotência é derivada do ID da campanha. Se o hook de campanha publicada for acionado duas vezes (e ele é — as entregas de webhook são pelo menos uma vez), a segunda chamada retorna o mesmo link sem criar uma duplicata. O campo metadata guarda as suas próprias chaves de junção para que possa correlacionar os eventos de clique da Elido de volta à campanha sem analisar tags.
Para atribuição de campanha de ponta a ponta com templates UTM e encaminhamento de conversão, o artigo principal sobre rastreamento UTM percorre todo o pipeline.
O que ainda não está na API#
Duas coisas frequentemente solicitadas, mas atualmente não disponíveis:
- Um GET de análise de link único que retorna todos os detalhamentos numa única chamada. O modelo atual exige chamadas separadas para cliques, país, referenciador, dispositivo e séries temporais. A agregação está no roteiro (roadmap); por enquanto, os SDKs paralelizam as requisições com um único método auxiliar.
- Replay de webhooks a partir da API. O dashboard expõe o histórico de entrega de webhooks e suporta o replay; a API ainda não. Isto também está no roteiro.
Se um recurso estiver na especificação OpenAPI, ele é suportado. Se estiver neste post, mas não na especificação, trate-o como planeado, em vez de garantido.
Leitura relacionada#
- Links inteligentes explicados — o artigo principal para o cluster de funcionalidades; cobre como o motor de redirecionamento resolve um link no edge.
- Webhooks vs polling para rastreamento de cliques — quando usar cada padrão de integração.
- Rastreamento de conversão no lado do servidor via links curtos — estendendo a API para o fluxo de reencaminhamento de conversão.
- Importação em massa de campanhas a partir do Google Sheets — um exemplo prático do endpoint de bulk.
- Passo a passo operacional: o guia do servidor MCP para ligar a superfície da API da Elido ao Claude, Cursor e outros clientes compatíveis com MCP.
- Superfície do produto:
/features/api-sdkse/solutions/developers.