Links API

Create, list, edit and delete LinkTrail smart links over the REST API: the link object, its routing fields, all-time totals and the QR code endpoint.

Note

Every endpoint on this page needs Authorization: Bearer <accessToken> and X-Workspace-Id: <id> — see Authentication. Paths are relative to https://backend.linktrail.io/v1.

MethodPathWhat it does
GET/linksList links, newest first (paginated)
GET/links/totalsAll-time link, click and install totals
POST/linksCreate a link (the slug is generated if you omit it)
GET/links/:linkIdLoad one link
PATCH/links/:linkIdEdit a link
DELETE/links/:linkIdDelete a link
GET/links/:linkId/qrA QR code image for the link's URL

Reading needs any role; creating, editing and deleting need editor, admin or owner. GET /links/resolve is the public endpoint the SDKs use — see SDK Endpoints.

LinkJSON
{ "id": 1, "slug": "summer-promo", "domainId": 1, "domainHost": "acme.linktrail.io",
  "url": "https://acme.linktrail.io/summer-promo",
  "campaignId": 1, "campaignName": "Summer Sale", "channelId": 1, "channelName": "Paid social",
  "deepLinkPath": "/products/42", "iosAppId": 3, "androidAppId": 4,
  "iosUrl": "https://apps.apple.com/app/id123",
  "androidUrl": "https://play.google.com/store/apps/details?id=com.acme.shop",
  "fallbackUrl": "https://acme.com", "iosInterstitial": true, "utm": { "utm_source": "instagram" },
  "customData": { "promoCode": "SUMMER" }, "status": "active",
  "clicks": 0, "installs": 0, "cvr": "—",
  "createdAt": "2026-05-28T00:00:00Z", "updatedAt": "2026-06-01T00:00:00Z" }
  • status is one of active, scheduled, expired or archived. Filter the list with ?status=active,expired (one value or several).
  • iosUrl and androidUrl are read-only: the server derives them from the apps you associate with iosAppId / androidAppId. Send null to clear an association.
  • iosInterstitial (default true) sends iOS visitors through the one-tap page that stages the click token for a deterministic deferred match; false redirects straight to the App Store, and iOS attribution then falls back to IP-and-platform matching — only if that matching is switched on, which it is not by default.
  • cvr is computed by the server. Single-link responses also carry iosAppName and androidAppName.
POST /v1/links — requestJSON
{ "domainId": 1, "slug": "summer-promo", "campaignId": 1, "channelId": 1,
  "deepLinkPath": "/products/42", "iosAppId": 3, "androidAppId": 4,
  "fallbackUrl": "https://acme.com", "iosInterstitial": true, "utm": { "utm_source": "instagram" },
  "customData": { "promoCode": "SUMMER" } }

A slug is letters, numbers, hyphens and underscores, unique per domain, and case-sensitive. Choosing one is a paid feature — Starter, Growth and Scale; on Free a request with slug answers 402. Leave it out on any plan and the server generates seven base58 characters. Creating a link past the plan's link limit also answers 402 (Free 100, Starter 1,000; Growth and Scale have no cap). An app id that is unknown, in another workspace or for the wrong platform answers 400.

Warning

PATCH /links/:linkId takes any subset of the same fields — slug and domainId included. Changing either changes the link's public URL: the old URL stops redirecting straight away, so anywhere it was already shared, printed or put in an ad breaks. (The dashboard shows both as read-only for that reason.)

Totals and QR codes

GET /v1/links/totals → 200JSON
{ "links": 137, "clicks": 7421330, "installs": 912004,
  "byStatus": {
    "active":    { "links": 112, "clicks": 6100230, "installs": 801220 },
    "scheduled": { "links": 0,   "clicks": 0,       "installs": 0 },
    "expired":   { "links": 8,   "clicks": 1201100, "installs": 99000 },
    "archived":  { "links": 17,  "clicks": 120000,  "installs": 11784 } } }

GET /links/:linkId/qr?format=svg|png&size=512 returns the image bytes (image/svg+xml or image/png), 64 to 2048 pixels.