API Documentation

A full REST API for links, QR codes, webhooks, UTM building, and analytics. Every endpoint below is authenticated with a per-account API key and rate-limited according to your plan.

Create a free account to generate an API key, or log in if you already have one.

Authentication

Every request must include your API key as a Bearer token in the Authorization header.

Authorization: Bearer YOUR_API_KEY

Generate and revoke keys from Dashboard → API Keys. A key inherits the rate limit and feature access of the plan its owner (or their team's billing owner) is on.

Rate Limits

Limits are enforced per minute, per API key, based on the key owner's plan. Every response includes:

HeaderDescription
X-RateLimit-LimitRequests allowed per minute on the current plan.
X-RateLimit-RemainingRequests left in the current window.

Exceeding the limit returns 429 Too Many Requests with a Retry-After header (seconds until the window resets).

Errors

StatusMeaning
401Missing or invalid API key.
403Payment overdue, or the request needs a plan feature your plan doesn't include.
404Resource not found, or it belongs to a different account (ownership is never leaked — a resource you don't own 404s, not 403s).
422Validation failed — see the errors object in the response body.
429Rate limit exceeded.
GET/api/v1/links

List your links, paginated (20 per page).

curl https://yourapp.test/api/v1/links \
  -H "Authorization: Bearer YOUR_API_KEY"
POST/api/v1/links

Create a short link.

FieldTypeRequiredNotes
urlstringYesDestination URL.
custom_aliasstringNoAlphanumeric/dash/underscore, max 20 chars. Auto-generated if omitted.
titlestringNo
passwordstringNoRequires a plan with password-protected links.
is_one_timebooleanNoExpires after the first click.
max_clicksintegerNo
expires_atdateNoISO 8601.
allowed_countriesarrayNoISO-2 country codes.
allowed_devicesarrayNodesktop, mobile, or tablet.
curl -X POST https://yourapp.test/api/v1/links \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/product", "custom_alias": "promo"}'
{
  "data": {
    "id": 42,
    "code": "promo",
    "short_url": "https://yourapp.test/promo",
    "destination_url": "https://example.com/product",
    "title": null,
    "is_one_time": false,
    "is_used": false,
    "is_active": true,
    "max_clicks": null,
    "click_count": 0,
    "expires_at": null,
    "password_protected": false,
    "allowed_countries": null,
    "allowed_devices": null,
    "created_at": "2026-01-01T12:00:00+00:00",
    "updated_at": "2026-01-01T12:00:00+00:00"
  }
}
GET/api/v1/links/{id}

Fetch a single link you own.

PUT/api/v1/links/{id}

Update url, title, is_active, max_clicks, or expires_at.

DELETE/api/v1/links/{id}

Delete a link. Returns 204 No Content.

QR Codes

GET/api/v1/qr

List your QR codes, paginated.

POST/api/v1/qr
FieldTypeRequiredNotes
typestringYesdynamic_link, wifi, vcard, or text_url.
link_idintegerIf type=dynamic_linkMust be a link you own.
namestringNo
payloadobjectDepends on typeType-specific fields (e.g. text for text_url).
foreground_color / background_colorstringNoHex, e.g. #000000.
sizeintegerNo100–2000px, default 400.
curl -X POST https://yourapp.test/api/v1/qr \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "dynamic_link", "link_id": 42, "name": "Storefront QR"}'
{
  "data": {
    "id": 7,
    "type": "dynamic_link",
    "name": "Storefront QR",
    "link_id": 42,
    "short_url": "https://yourapp.test/promo",
    "foreground_color": "#000000",
    "background_color": "#FFFFFF",
    "size": 400,
    "margin": 4,
    "downloads": {
      "png": "https://yourapp.test/storage/qrcodes/7.png",
      "svg": "https://yourapp.test/storage/qrcodes/7.svg",
      "pdf": "https://yourapp.test/storage/qrcodes/7.pdf"
    },
    "created_at": "2026-01-01T12:00:00+00:00",
    "updated_at": "2026-01-01T12:00:00+00:00"
  }
}
GET/api/v1/qr/{id}
PUT/api/v1/qr/{id}
DELETE/api/v1/qr/{id}
POST/api/v1/bulk/qr

Upload a CSV (name,url columns) to create many links + dynamic QR codes at once. Processed asynchronously.

curl -X POST https://yourapp.test/api/v1/bulk/qr \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "csv=@products.csv"
{ "message": "CSV accepted, processing in the background." }

Webhooks

GET/api/v1/webhooks
POST/api/v1/webhooks
FieldTypeRequiredNotes
urlstringYesEndpoint you control.
subscribed_eventsarrayYesOne or more of: link.clicked, link.expired, link.limit_reached, qr.scanned, bio_page.viewed.
curl -X POST https://yourapp.test/api/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://yourapp.com/hooks/inbound", "subscribed_events": ["link.clicked"]}'
{
  "data": {
    "id": 3,
    "url": "https://yourapp.com/hooks/inbound",
    "secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "subscribed_events": ["link.clicked"],
    "is_active": true,
    "last_fired_at": null,
    "last_status_code": null,
    "last_error_message": null,
    "created_at": "2026-01-01T12:00:00+00:00"
  }
}

Every delivery is signed — verify it with HMAC-SHA256 over the raw request body using the webhook's secret, sent in the X-Webhook-Signature header.

GET/api/v1/webhooks/{id}
PUT/api/v1/webhooks/{id}
DELETE/api/v1/webhooks/{id}

UTM Builder

POST/api/v1/utm/build

Stateless helper — appends UTM parameters to a URL without creating any resource.

curl -X POST https://yourapp.test/api/v1/utm/build \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com", "utm_source": "newsletter", "utm_medium": "email", "utm_campaign": "spring_sale"}'
{
  "built_url": "https://example.com?utm_source=newsletter&utm_medium=email&utm_campaign=spring_sale"
}

Analytics

All analytics endpoints are scoped to a link you own.

GET/api/v1/analytics/links/{id}/summary
{
  "data": {
    "total_clicks": 1280,
    "unique_clicks": 940,
    "max_clicks": null,
    "is_active": true,
    "last_clicked_at": "2026-01-05T08:12:00+00:00"
  }
}
GET/api/v1/analytics/links/{id}/timeseries?days=30

Daily click counts for the last days (default 30).

GET/api/v1/analytics/links/{id}/breakdown?by=country

by is one of country (default), device, browser, or referrer.

GET/api/v1/analytics/links/{id}/campaigns

Click counts grouped by utm_campaign.

GET/api/v1/analytics/links/{id}/variants

Click counts per A/B test variant, alongside each variant's configured weight.