14 min de leituraRecursos

API do encurtador de URL: um guia rápido de 30 minutos em cinco linguagens

Do zero a uma automação de link curto funcionando em TypeScript, Python, Go, Ruby e PHP - autenticação, idempotência, tratamento de erro e as pegadinhas de produção.

Marius Voß
DevRel · edge infra
Diagrama de guia rápido em cinco linguagens com painéis de código para TypeScript, Python, Go, Ruby e PHP, todos apontando para um endpoint central da API do Elido

Uma API de encurtador de URL é uma das integrações menores no backlog de uma equipe de engenharia típica. Três endpoints, um header de autenticação, um payload JSON. A página da documentação promete a primeira chamada em cinco minutos. Então o tráfego de produção chega, a lógica de retry cria links duplicados, o painel se enche de variantes /foo-1, /foo-2, /foo-3 do mesmo destino, e alguém abre um ticket.

Este post percorre a integração de verdade. Autenticação, a primeira chamada, os quatro endpoints que cobrem a maioria dos casos de uso, idempotência, tratamento de erro, limites de taxa, e as pegadinhas de produção que o guia rápido de cinco minutos pula. Exemplos de código em TypeScript, Python, Go, Ruby e PHP - as três primeiras pelos SDKs oficiais (@elido/sdk, elido-python, github.com/elido/elido-go), as duas últimas por clientes HTTP simples.

Pré-requisitos

Entre no painel, navegue até /dashboard/api-keys, e crie uma chave de API (ela começa com elido_). Tokens são escopados por workspace - um token emitido no workspace A não pode criar links no workspace B. Tokens de usuário-máquina (para sistemas de CI, ferramentas internas, integração máquina-a-máquina) são criados em /dashboard/machine-users e rotacionam independentemente das chaves pessoais. Os dois tipos carregam um papel de workspace pré-definido (viewer, editor ou admin) em vez de escopos por endpoint, então dê a um job de CI o papel editor se ele só cria links. O guia sobre permissões de chaves de API lista o que cada papel pode acessar, incluindo por que alterações em webhooks exigem admin.

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 criar, modificar e ler os links.

A especificação OpenAPI é publicada em https://elido.app/openapi.json e segue o 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 - usando 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'];

As 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 slug na requisição. O alfabeto do slug é base62 ([0-9A-Za-z]); o comprimento padrão é seis caracteres.

Os quatro endpoints que você realmente vai usar

A API tem mais de quatro endpoints, mas a maioria das integrações fica dentro desse conjunto.

Diagrama em estrela dos quatro endpoints centrais de link ao redor do recurso /v1/links: POST criar, GET ler, PATCH atualizar, e DELETE excluir, cada um com sua principal pegadinha.

POST /v1/links aceita a URL de destino mais campos opcionais:

  • slug - um slug que você escolhe (precisa ser único no domínio).
  • domain_id - para links de domínio personalizado; em /v1/links, o domínio curto padrão do seu plano é usado se omitido. O caminho com escopo de workspace /v1/workspaces/{workspace_id}/links exige esse campo.
  • title - um rótulo exibido no painel.
  • tags - um array de strings livres para organização.
  • expires_at - timestamp RFC 3339 após o qual o link retorna 410 Gone.
  • redirect_status - 301, 302 (o padrão) ou 307.
  • password - ainda não aceito na criação; defina-o com um PATCH logo depois, e o redirecionamento exibirá uma página de senha antes de encaminhar.
  • utm e metadata - planejados. Hoje, coloque os parâmetros UTM diretamente em destination_url e mantenha suas próprias chaves de junção em tags.

O slug personalizado é o campo que morde as equipes em produção. Se você passa um slug já em uso por outro link no mesmo domínio, a API retorna 409 Conflict. O tratador de retry ingênuo que acrescenta um contador (my-slug-1, my-slug-2) produz o problema de link duplicado descrito na abertura. O comportamento de retry correto está descrito na seção de idempotência abaixo.

GET /v1/links/{id} retorna o registro completo do link, incluindo short_url e toda a configuração. As contagens de cliques não estão no registro do link; elas vêm dos endpoints de analytics abaixo. O ID do link é o identificador canônico - slugs podem mudar, IDs não.

GET /v1/links?host=…&tags=…&limit=… lista links no workspace com filtros. A paginação é baseada em cursor; next_cursor na resposta é opaco e volta como o parâmetro de query cursor na próxima requisição.

PATCH /v1/links/{id} aceita os mesmos campos que a criação. As atualizações mais comuns: mudar a URL de destino (útil para rotação de campanha sem reimprimir QR codes), mudar tags, estender expires_at. O slug é alterado pelo mesmo PATCH, enviando um novo slug. O slug antigo deixa de resolver imediatamente; um endpoint de renomeação dedicado que mantém um 301 do slug antigo por um período de retenção está planejado, não foi criado.

DELETE /v1/links/{id} faz exclusão reversível e retorna 204 No Content. O link deixa de redirecionar e desaparece das chamadas de listagem e leitura. Uma visão de lixeira com endpoint de restauração e uma janela de 90 dias antes da exclusão permanente está planejada; hoje não há uma chamada de API que traga um link excluído de volta.

Chaves de idempotência

Toda requisição mutante - POST, PATCH, DELETE - aceita um header Idempotency-Key. O valor do header é uma string opaca de até 255 caracteres; o servidor armazena o corpo da resposta e o código de status por 24 horas, chaveado em (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 fornecidas. Você pode sobrescrever:

const link = await elido.links.create(
  { destinationUrl: "https://shop.example.com/spring-sale" },
  { idempotencyKey: "order-12345-link" },
);

O caso de uso é um loop de retry. Se 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 nova tentativa do mesmo job vê a mesma chave, acerta o cache de idempotência, e retorna o link criado originalmente em vez de produzir um segundo.

Pipeline onde um hook de campanha at-least-once dispara duas chamadas de criação carregando a mesma chave de idempotência; o cache de 24 horas deduplica a segunda para que exatamente um link seja criado.

A principal pegadinha: o cache de idempotência vive por 24 horas, não para sempre. Uma nova tentativa no terceiro dia de um job travado vai criar um novo link. Se a integração roda 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 pegadinha: idempotência é por workspace. A mesma chave em dois workspaces cria dois links. Essa é a semântica correta para uma API multi-workspace, mas pode surpreender equipes que assumem que a chave é globalmente única.

Tratamento de erro

A API retorna códigos de status HTTP padrão mais 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 você verá com mais frequência:

  • 400 invalid_request - falha de validação do payload. O campo message lista os campos específicos. Não tente novamente; corrija o payload.
  • 401 unauthorized - token ausente ou inválido. Não tente novamente sem rotacionar o token.
  • 403 forbidden - o papel do token não permite a ação (uma chave viewer não pode criar links). Verifique o papel da chave em /dashboard/api-keys.
  • 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 vazar a existência do recurso a chamadores não autorizados).
  • 409 conflict - slug já em uso, ou edição simultânea detectada (PATCH em uma versão desatualizada). Busque novamente e tente de novo.
  • 429 rate_limit_exceeded - recue conforme o valor de retry_after.
  • 500 internal_server_error - falha do lado do servidor. Seguro para tentar novamente com a mesma chave de idempotência.
  • 502 bad_gateway, 503 service_unavailable, 504 gateway_timeout - problemas de infraestrutura transitórios. Recue e tente novamente.

Os SDKs oficiais implementam backoff exponencial com jitter para 429, 500, 502, 503 e 504. Eles não tentam novamente em 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; tentar novamente um 400 com o mesmo payload não vai produzir um resultado diferente.

Divisão de decisão classificando códigos de status da API em uma coluna de tentar-novamente-com-backoff (429, 500, 502, 503, 504) e uma coluna de não-tentar-novamente (400, 401, 403, 404, 409) para erros de programação e conflito.

O request_id no corpo do erro é o campo a incluir em tickets de suporte. Podemos rastrear qualquer requisição a partir desse ID pelo log de auditoria, pelo log da aplicação, e pelas métricas da plataforma - e não conseguimos rastrear uma requisição sem ele.

Limites de taxa

Os limites de taxa publicados são 100 requisições por segundo por workspace no Pro, 500 no Business, e um limite negociado no Enterprise. O nível gratuito é 10 req/s.

O estado do limite de taxa é exposto em três headers de resposta em toda resposta da API:

  • X-RateLimit-Limit - o limite por segundo atual.
  • X-RateLimit-Remaining - requisições restantes no segundo atual.
  • X-RateLimit-Reset - timestamp Unix de quando o bucket é reiniciado.

O limite de 100/s é uma implementação de token bucket com capacidade de burst de 200 - o que significa que você pode emitir 200 requisições de uma vez se o bucket estiver cheio, e então se estabilizar na taxa sustentada de 100/s. A maioria dos jobs de criação de link curto cabe confortavelmente no burst; integrações pesadas em analytics que percorrem eventos de clique históricos se beneficiam da folga do nível Pro.

Para operações em massa, o endpoint POST /v1/links/bulk aceita até 100 links por requisição e conta como uma única unidade de limite de taxa. Esse é o endpoint certo para qualquer job que crie mais de cem links de uma vez. Para o tratamento mais profundo sobre ritmo contra o token bucket, escolher quais códigos de status tentar novamente, e como as chaves de idempotência evitam que novas tentativas dupliquem links, veja limites de taxa, retries e idempotência em produção.

O que os SDKs fazem que o HTTP simples não faz

Os SDKs oficiais entregam quatro coisas que se pagam rapidamente:

  • Retry automático com backoff para os códigos de status que permitem nova tentativa.
  • Geração de chave de idempotência quando não fornecida explicitamente.
  • Erros tipados para que você possa fazer catch (err) { if (err instanceof ElidoRateLimitError) { … } } em vez de analisar JSON em blocos catch.
  • Iteradores de paginação para que endpoints de listagem exponham iteradores assíncronos ou geradores em vez de exigir manipulação manual de cursor.

O SDK de Go também expõe o cliente HTTP subjacente para instrumentação - útil se você quer conectá-lo à sua configuração de tracing existente. A página do recurso API + SDKs do repositório cobre a superfície completa; a referência da API é publicada em /docs/api-reference.

Acesso a analytics

Os endpoints de analytics são somente leitura e vivem sob /v1/workspaces/{id}/analytics/; o guia da API de analytics de links lista todos os relatórios, seus parâmetros e o formato de suas respostas. As consultas mais comuns:

  • GET .../clicks/recent?from=…&to=… - cliques individuais, do mais recente para o mais antigo, paginados com next_cursor. Útil para pipelines de exportação.
  • GET .../timeseries?from=…&to=…&interval=day - contagens de clique agrupadas para um intervalo de tempo; interval é hour ou day, e tz define o fuso horário dos agrupamentos.
  • GET .../breakdown/country?from=…&to=… - detalhamento geográfico.
  • GET .../breakdown/referrer?from=…&to=… - detalhamento por referrer.

Os outros relatórios são summary, links/top, os demais detalhamentos (host, device, browser, destination) e as listas de mais acessados (top-countries, top-regions, top-cities, top-referrers, top-destinations). from e to são datas em YYYY-MM-DD e to é exclusivo; adicione link_id para restringir qualquer relatório a um link, e limit para definir o tamanho dos detalhamentos e das listas de mais acessados.

O feed de eventos de clique brutos é o maior. Um workspace com 10 milhões de cliques por mês produz cerca de 600MB de dados de evento bruto em JSON por mês. Para exportações nessa escala, o guia de exportação de analytics cobre o mecanismo de exportação em massa que contorna o envelope JSON e transmite diretamente do data warehouse de analytics.

Webhooks são o inverso do polling - em vez de você perguntar à API o que mudou, a API entrega eventos de link e domínio ao seu endpoint. Configure em /dashboard/webhooks:

await elido.webhooks.create({
  url: "https://your-app.example/webhooks/elido",
  events: ["link.created", "link.updated", "link.expired"],
  secret: process.env.WEBHOOK_SIGNING_SECRET,
});

Um evento click.created por clique está no roadmap, mas ainda não disponível, então hoje os dados de clique vêm dos endpoints de analytics. Cada entrega inclui um header X-Elido-Signature (também enviado como X-Webhook-Signature) com o valor v1=<hex>: um HMAC-SHA256, chaveado com o segredo do seu endpoint, sobre o valor de X-Webhook-Timestamp, um ponto e o corpo bruto da requisição. Verifique a assinatura antes de processar - sem isso, qualquer chamador pode postar no seu endpoint de webhook e se passar pelo Elido.

A semântica de entrega é at-least-once: uma entrega que falha é tentada novamente com backoff de minutos, com três tentativas no total por padrão. Para o formato detalhado e o comportamento de retry, o post webhooks vs polling 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 se parece com isto. Sua automação de marketing cria uma campanha no Customer.io ou no HubSpot. Um hook dispara quando a campanha é publicada. Seu manipulador cria o link curto, o anexa ao registro da campanha, e o envia 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}`,
        `batch:${campaign.batchId}`,
        campaign.channel,
      ],
    },
    {
      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 disparar duas vezes (isso acontece - entregas de webhook são at-least-once), a segunda chamada retorna o mesmo link sem criar uma duplicata. As tags campaign: e batch: carregam suas próprias chaves de junção para que você possa correlacionar os eventos de clique do Elido de volta à campanha; um campo metadata dedicado para isso está planejado. Os parâmetros UTM pertencem à própria campaign.destinationUrl até que o campo utm seja lançado.

Para atribuição de campanha de ponta a ponta com templates de UTM e encaminhamento de conversão, o conteúdo principal de rastreamento de UTM percorre o pipeline completo.

O que ainda não está na API

Duas coisas comumente perguntadas, atualmente não disponíveis:

  • Um único GET de analytics de link que retorna todos os detalhamentos em uma chamada. O modelo atual exige chamadas separadas para cliques, país, referrer, dispositivo e série temporal. A agregação está no roadmap; por enquanto, faça as requisições em paralelo a partir do seu próprio código.
  • Replay de webhook a partir da API. O painel expõe o histórico de entrega de webhook e aceita replay; a API ainda não. Isso também está no roadmap.

Se um recurso está na especificação OpenAPI, ele é suportado. Se está neste post mas não na especificação, trate-o como planejado em vez de garantido.

Leitura relacionada

Experimente Elido

Cole uma URL, obtenha um link curto

Sem cadastro. O link vive 30 dias. Cadastre-se para mantê-lo para sempre.

Grátis, sem necessidade de registo · 2 por dia

Experimente o Elido

Encurtador de URL hospedado na UE: domínios personalizados, análises profundas e API aberta. Plano gratuito - sem cartão de crédito.

Tags
url shortener api
bitly api alternative
link shortener api
rest api short link
url shortener sdk
openapi 3.1
idempotency keys

Continuar lendo