Skip to content

API reference

Build on tiny.hygr.dev

A small REST API for creating short links and rendering QR codes, plus webhooks so your systems hear about scans. Create a key under Developers.

Authentication

Send your key as a bearer token on every request. Keys look like tiny_… and are shown once when created — we only keep a hash, so a lost key has to be revoked and replaced.

Authorization: Bearer tiny_your_key_here

Responses are JSON. Errors carry an error object:401 missing or unknown key · 403 your plan does not include the API · 404 not found · 422 invalid input · 429 rate limited.

Rate limit: 120 requests per minute per key. Every response carries X-RateLimit-Remaining, and a 429 carries Retry-After. CORS is open, so you can call the API from a browser.

Endpoints

GET/api/v1/links

List your short links, newest first.

curl https://preview.tiny.hygr.dev/api/v1/links \
  -H "Authorization: Bearer tiny_your_key_here"
POST/api/v1/links

Create a short link. The destination is normalised and validated.

Body: { "url": "https://example.com/page" }

curl -X POST https://preview.tiny.hygr.dev/api/v1/links \
  -H "Authorization: Bearer tiny_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/launch"}'

# 201 Created
{
  "data": {
    "slug": "abc1234",
    "shortUrl": "https://preview.tiny.hygr.dev/abc1234",
    "url": "https://example.com/launch",
    "clicks": 0,
    "createdAt": "2026-09-15T08:00:00.000Z"
  }
}
GET/api/v1/links/{slug}

Fetch one link, including its click count.

curl https://preview.tiny.hygr.dev/api/v1/links/abc1234 \
  -H "Authorization: Bearer tiny_your_key_here"
PATCH/api/v1/links/{slug}

Re-point a link. This is how you change where an already-printed dynamic QR code goes.

Body: { "url": "https://example.com/new-page" }

curl -X PATCH https://preview.tiny.hygr.dev/api/v1/links/abc1234 \
  -H "Authorization: Bearer tiny_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/autumn"}'
DELETE/api/v1/links/{slug}

Delete a link. Anything pointing at it, including printed codes, stops working.

curl -X DELETE https://preview.tiny.hygr.dev/api/v1/links/abc1234 \
  -H "Authorization: Bearer tiny_your_key_here"
POST/api/v1/qr

Render a QR code as SVG. Returns the SVG directly, or JSON with a data URL when format is json.

Body: { "data": "https://example.com", "style": { … }, "format": "svg" | "json" }

curl -X POST https://preview.tiny.hygr.dev/api/v1/qr \
  -H "Authorization: Bearer tiny_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"data":"https://example.com","style":{"dotColor":"#121358"}}' \
  -o code.svg

Webhooks

Add an endpoint under Developers and we will POST JSON to it. Events: link.created, link.scanned, qr.created.

Payload

POST https://your-endpoint.example.com
X-Tiny-Signature: <hex hmac>
X-Tiny-Timestamp: 1789000000

{
  "event": "link.scanned",
  "createdAt": "2026-09-15T08:00:00.000Z",
  "data": { "slug": "abc1234", "url": "https://example.com/launch" }
}

Verifying a delivery

The signature is an HMAC-SHA256 of <timestamp>.<raw body> using your endpoint’s signing secret. Compare it in constant time, and reject anything with a timestamp more than a few minutes old so a captured delivery cannot be replayed.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(rawBody, headers, secret) {
  const signature = headers["x-tiny-signature"];
  const timestamp = headers["x-tiny-timestamp"];

  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(String(signature));
  return a.length === b.length && timingSafeEqual(a, b);
}

Respond with a 2xx quickly. Deliveries are fire-and-forget with a short timeout, so a slow endpoint is recorded as a failure rather than holding anything up.