# Criar link curto

> POST /v1/links no curtei: parâmetros, exemplos em cURL, JavaScript, PHP e Python, resposta e erros. Também listar, editar e apagar links.

Fonte: https://curtei.com/docs/api/links

Endpoint: `POST https://api.curtei.com/v1/links`
Escopo: `links:write`

Cria um link curto para uma URL. Só `url` é obrigatório. A conta precisa ter o e-mail confirmado.

## Parâmetros do corpo

| Campo | Tipo | Descrição |
| --- | --- | --- |
| `url` (obrigatório) | string | Destino http ou https. Passa pela checagem de segurança (Google Web Risk e lista de bloqueio). |
| `domain` | string | Domínio do link. Padrão: o domínio padrão da conta (`l.curtei.com`, se você não tiver um próprio). |
| `slug` | string | Código personalizado (`promo-natal`). Só em domínio próprio; no compartilhado o código é sempre aleatório. |
| `title` | string | Nome para achar o link no painel. |
| `redirect_type` | 301 ou 302 | Tipo do redirecionamento. Padrão: 302. |
| `expires_at` | ISO-8601 | Depois disso o link para de redirecionar. |
| `max_clicks` | inteiro | Limite de cliques; ao atingir, o link expira. |
| `password` | string | 8 a 128 caracteres. O visitante digita a senha antes de seguir; a resposta traz `has_password`. |
| `utm` | objeto | `source`, `medium`, `campaign`, `term`, `content`, anexados ao destino. |
| `tags` | string[] | Etiquetas para filtrar e somar métricas. |
| `campaign_id` | uuid | Vincula o link a uma [campanha](https://curtei.com/docs/api/campanhas). |
| `external_id` | string | Seu identificador (pedido, cliente). Buscável por `GET /links/by-external-id/{external_id}`. |
| `metadata` | objeto | Dados livres seus, até 4 KB; voltam nas respostas e nos webhooks. |
| `track_clicks` | boolean | Registrar cliques. Padrão: `true`. |
| `track_conversions` | boolean | Requer `track_clicks`. Anexa `wsc_id` ao destino para registrar [conversões](https://curtei.com/docs/api/conversoes). |
| `open_in_app` | boolean | No celular, tenta abrir o app (YouTube, Instagram, WhatsApp, TikTok…) em vez do navegador. |
| `hide_referrer` | boolean | O destino não fica sabendo de qual página veio o clique. |
| `rules` / `variants` | objeto[] | [Roteamento e teste A/B](https://curtei.com/docs/api/roteamento). |

## Exemplo

cURL:

```bash
curl -X POST https://api.curtei.com/v1/links \
  -H "Authorization: Bearer $CURTEI_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"url":"https://minhaloja.com.br/promo","tags":["newsletter"]}'
```

JavaScript:

```js
const res = await fetch("https://api.curtei.com/v1/links", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CURTEI_TOKEN}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({ url: "https://minhaloja.com.br/promo", tags: ["newsletter"] }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const link = await res.json();
console.log(link.short_url);
```

PHP:

```php
$ch = curl_init('https://api.curtei.com/v1/links');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer '.getenv('CURTEI_TOKEN'),
        'Content-Type: application/json',
        'Idempotency-Key: '.bin2hex(random_bytes(16)),
    ],
    CURLOPT_POSTFIELDS => json_encode(['url' => 'https://minhaloja.com.br/promo', 'tags' => ['newsletter']]),
]);
$link = json_decode(curl_exec($ch), true);
echo $link['short_url'];
```

Python:

```python
import os, uuid, requests

res = requests.post(
    "https://api.curtei.com/v1/links",
    headers={
        "Authorization": f"Bearer {os.environ['CURTEI_TOKEN']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"url": "https://minhaloja.com.br/promo", "tags": ["newsletter"]},
    timeout=10,
)
res.raise_for_status()
print(res.json()["short_url"])
```

## Resposta 201

```json
{
  "id": "0193a0b2-7c1e-7d3a-9f10-5b2c8e4a1d22",
  "short_url": "https://l.curtei.com/aB3xK9",
  "slug": "aB3xK9",
  "domain": "l.curtei.com",
  "url": "https://minhaloja.com.br/promo",
  "status": "active",
  "has_password": false,
  "qr_code_url": "https://api.curtei.com/v1/links/0193a0b2-7c1e-7d3a-9f10-5b2c8e4a1d22/qr",
  "total_clicks": 0,
  "unique_clicks": 0,
  "external_id": null,
  "metadata": null,
  "created_at": "2026-11-13T21:30:00Z"
}
```

## Erros comuns

| HTTP | code | O que fazer |
| --- | --- | --- |
| 400 | `invalid_url` | Envie uma URL completa, com `https://`. |
| 403 | `email_not_verified` | Confirme o e-mail da conta. |
| 409 | `slug_taken` | Escolha outro `slug` ou omita para gerar um aleatório. |
| 422 | `url_blocked` | O destino foi classificado como inseguro; `details.reason` diz o motivo. |
| 422 | `custom_slug_requires_own_domain` | Cadastre um [domínio próprio](https://curtei.com/docs/api/dominios) ou não envie `slug`. |
| 429 | `creation_rate_exceeded` | Espere `details.retry_after` segundos. |

## Listar links

`GET /links` (escopo `links:read`). Filtros: `tag`, `campaign_id`, `external_id`, `status`, `domain`, `search` (título, destino ou código), `created_from`, `created_to`, `sort` (`created_at` ou `total_clicks`), `limit` (até 100) e `cursor`.

```bash
curl "https://api.curtei.com/v1/links?tag=newsletter&limit=50" \
  -H "Authorization: Bearer $CURTEI_TOKEN"
```

## Obter, editar e apagar

| Rota | Uso |
| --- | --- |
| `GET /links/{id}` | Um link pelo id |
| `GET /links/by-external-id/{external_id}` | Um link pelo seu identificador |
| `PATCH /links/{id}` | Muda `url`, `title`, `expires_at`, `max_clicks`, `status` (`active`/`disabled`), `tags`, `utm`, `metadata`, `password`, `rules`, `variants`. O `slug` não muda. |
| `DELETE /links/{id}` | Apaga. O código fica reservado por 90 dias. |

Trocar a `url` de um link já divulgado é o jeito de mudar o destino sem trocar o link curto nem reimprimir o QR Code. A nova URL passa pela mesma checagem de segurança.
