loq.su

REST API для сокращения ссылок

Базовый URL: https://loq.su. Все ответы в JSON, кодировка UTF-8.

Создать ссылку

POST /api/links

Тело запроса

ПолеТипОбязательноеОписание
urlstringдаhttp(s)-URL до 2048 символов; не может указывать на сам loq.su
aliasstringнетсвой slug, [a-zA-Z0-9_-]{3,32}, не из зарезервированных (api, admin, metrics, …)
expires_atRFC3339 stringнетUTC-таймстамп истечения, минимум на минуту в будущее
max_clicksinteger > 0нетлимит переходов; после — 410 Gone

Ответ 201 Created

{
  "slug":       "abc123",
  "short_url":  "https://loq.su/abc123",
  "target_url": "https://example.com/long",
  "expires_at": "2026-12-31T00:00:00Z",
  "max_clicks": 100,
  "created_at": "2026-04-27T12:00:00Z"
}

Перейти по ссылке

GET /:slug

Возвращает 302 Found с Location: <target_url> и атомарно инкрементит счётчик. Логирует структурированный JSON со slug, target host, статусом, UA, referer и SHA-256-хешем IP.

  • 404 — slug не найден
  • 410 — ссылка забанена / истекла / лимит исчерпан

Предпросмотр

GET /preview/:slug

HTML-страница с целевым URL и кнопкой «Перейти». Тот же экран автоматически отдаётся для /:slug, если хост цели совпадает с записью в blocklist (защита от фишинга/малвара).

Ошибки

КодКогда
400невалидный JSON / URL / alias / expires_at
409такой alias уже занят
422хост целевого URL в blocklist
429превышен rate-limit
503Redis недоступен (rate-limiter оффлайн)

Тело: {"error": "<человекочитаемое сообщение>"}.

Rate limits

Ограничение фиксируется по хешу IP-адреса клиента (через X-Forwarded-For). Никаких токенов / ключей не требуется.

  • 10 создаваний в минуту с одного IP
  • 100 создаваний в час с одного IP

На редиректы лимита нет (только глобальный nginx safety net).

Примеры

curl — простое

curl -s -X POST https://loq.su/api/links \
  -H 'content-type: application/json' \
  -d '{"url":"https://example.com/long"}'

curl — с алиасом и лимитами

curl -s -X POST https://loq.su/api/links \
  -H 'content-type: application/json' \
  -d '{
    "url":        "https://example.com/promo",
    "alias":      "summer-26",
    "expires_at": "2026-09-01T00:00:00Z",
    "max_clicks": 1000
  }'

Python

import urllib.request, json
req = urllib.request.Request(
    "https://loq.su/api/links",
    method="POST",
    headers={"content-type": "application/json"},
    data=json.dumps({"url": "https://example.com"}).encode(),
)
with urllib.request.urlopen(req) as r:
    print(json.loads(r.read())["short_url"])

JavaScript (browser)

const r = await fetch('https://loq.su/api/links', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body:    JSON.stringify({ url: 'https://example.com' }),
});
const { short_url } = await r.json();
console.log(short_url);