Links

Create, update, delete, and list short links. Supports custom slugs, domains, tags, passwords, expiration, smart routing, weighted rotation, and custom OG metadata.

The Link Object

Every link endpoint returns the same standardized object shape:

FieldTypeDescription
idstringUnique link ID (UUID)
slugstringThe short slug (e.g. "my-campaign")
urlstringThe destination URL
shortUrlstringFull short URL (e.g. "https://flyn.to/my-campaign")
domainstringDomain used for this link
titlestring | nullDisplay title for the link
clicksnumberTotal click count
statusstring"active", "expired", or "archived"
tagsstring[]Array of tags for organizing links
createdAtstringISO 8601 creation timestamp
expiresAtstring | nullISO 8601 expiration timestamp, or null
folderIdstring | nullFolder ID if link is in a folder
passwordbooleanWhether the link is password-protected (actual value is never exposed)
ogTitlestring | nullCustom Open Graph title
ogDescriptionstring | nullCustom Open Graph description
ogImagestring | nullCustom Open Graph image URL
targetingobject | nullDevice/geo targeting rules (see Smart Routing below)
cloakingbooleanWhether link cloaking is enabled
noIndexbooleanWhether search engine indexing is blocked
notesstring | nullPrivate notes about the link
rotationobject[] | nullWeighted rotation destinations, or null when the link serves url only (see Link Rotation below)
abUrlBstring | nullLegacy A/B variant URL. Still supported as a 2-destination compatibility view of rotation
abSplitnumber | nullLegacy A/B split: percent of traffic served the primary url, the rest goes to abUrlB
Example Link Object
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "slug": "my-campaign",
  "url": "https://example.com/landing-page?utm_source=twitter",
  "shortUrl": "https://flyn.to/my-campaign",
  "domain": "www.flyn.to",
  "title": "example.com",
  "clicks": 1423,
  "status": "active",
  "tags": ["marketing", "q1-2026"],
  "createdAt": "2026-03-15T10:30:00.000Z",
  "expiresAt": null,
  "folderId": null,
  "password": false,
  "ogTitle": "Check out our new product",
  "ogDescription": "The fastest way to manage links",
  "ogImage": "https://example.com/og-image.png",
  "targeting": null,
  "cloaking": false,
  "noIndex": false,
  "notes": null,
  "rotation": null,
  "abUrlB": null,
  "abSplit": null
}

Create a Link

Creates a new short link. If no slug is provided, a random 6-character slug is generated.

POST/api/links

Request Body

ParameterTypeRequiredDescription
urlstringYesThe destination URL to shorten. Auto-prepends https:// if no protocol is provided.
slugstringNoCustom slug (letters, numbers, hyphens, underscores). Max 100 chars. Auto-generated if omitted.
domainstringNoDomain to use (must be a verified custom domain). Defaults to "www.flyn.to".
titlestringNoDisplay title. Defaults to the destination hostname.
tagsstring[]NoArray of tags for organizing links.
passwordstringNoPassword-protect the link. Users must enter the password before being redirected.
expiresAtstringNoISO 8601 expiration date. Link returns 410 Gone after this date.
folderIdstringNoUUID of a folder to organize the link into.
ogTitlestringNoCustom Open Graph title for link previews.
ogDescriptionstringNoCustom Open Graph description for link previews.
ogImagestringNoCustom Open Graph image URL. Must be public HTTPS URL.
targetingobjectNoSmart routing rules (see Smart Routing section below).
cloakingbooleanNoEnable link cloaking, shows your short URL in the browser address bar.
noIndexbooleanNoAdd noindex meta tag to prevent search engines from indexing.
notesstringNoPrivate notes about the link (not visible to clickers).
rotationobject[]NoUp to 12 weighted destinations to split traffic between (see Link Rotation below). Pro or Lifetime only.
Request Example
curl -X POST https://www.flyn.to/api/links \
  -H "Authorization: Bearer flyn_sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/landing-page",
    "slug": "my-campaign",
    "tags": ["marketing", "q1-2026"],
    "ogTitle": "Check out our new product"
  }'

Response (201)

Returns the full Link object. Triggers link.create webhook event.

Error Codes

ParameterTypeRequiredDescription
400errorNoInvalid URL, slug format, or missing required fields
403errorNoA Pro-only field was set on a free account. Body carries code "UPGRADE_REQUIRED" and premiumFields, e.g. ["rotation"]
409errorNoSlug is already taken on this domain
422errorNoA destination failed the safety scan. Body carries code "DESTINATION_BLOCKED"
429errorNoRate limit exceeded (10 req/min)

List Links

Returns a paginated list of your links with optional filtering and sorting.

GET/api/links

Query Parameters

ParameterTypeRequiredDescription
pagenumberNoPage number (default: 1)
limitnumberNoItems per page (1-100, default: 50)
statusstringNoFilter by status: "active", "expired", or "archived"
searchstringNoSearch in slug, destination URL, and title
tagstringNoFilter by tag name
folder_idstringNoFilter by folder UUID
sortstringNoSort by: "created_at" (default), "clicks", "slug", or "title"
orderstringNo"asc" or "desc" (default: desc)
Request Example
curl "https://www.flyn.to/api/links?page=1&limit=20&status=active&tag=marketing&sort=clicks&order=desc" \
  -H "Authorization: Bearer flyn_sk_live_..."

Response (200)

Response
{
  "links": [ /* array of Link objects */ ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 142,
    "totalPages": 8,
    "hasMore": true
  }
}

Get a Link

Retrieves a single link by ID.

GET/api/links/:id

Path Parameters

ParameterTypeRequiredDescription
idstringYesThe UUID of the link

Returns the full Link object, or 404 if not found.

Update a Link

Updates one or more fields on an existing link. Only include the fields you want to change.

PATCH/api/links/:id

Request Body

ParameterTypeRequiredDescription
urlstringNoNew destination URL
slugstringNoNew slug (must be unique on the domain)
domainstringNoNew domain (must be a verified custom domain)
titlestringNoNew display title
statusstringNoNew status: "active", "expired", or "archived"
tagsstring[]NoReplace all tags
passwordstringNoSet or update password
expiresAtstringNoNew expiration date (ISO 8601)
folderIdstringNoMove to a different folder
ogTitlestringNoUpdate custom OG title
ogDescriptionstringNoUpdate custom OG description
ogImagestringNoUpdate custom OG image URL
targetingobjectNoUpdate smart routing rules
cloakingbooleanNoEnable/disable link cloaking
noIndexbooleanNoEnable/disable noindex
notesstringNoUpdate private notes
rotationobject[]NoReplace the whole rotation list. Send [] to stop rotating (see Link Rotation below). Pro or Lifetime only.
Request Example
curl -X PATCH https://www.flyn.to/api/links/a1b2c3d4-... \
  -H "Authorization: Bearer flyn_sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "tags": ["marketing", "updated"], "status": "archived" }'

Returns the updated Link object. Triggers link.update webhook event.

Delete a Link

Permanently deletes a link and all associated click data. This action cannot be undone.

DELETE/api/links/:id

Path Parameters

ParameterTypeRequiredDescription
idstringYesThe UUID of the link to delete

Response (200)

{ "deleted": true, "id": "a1b2c3d4-..." }

Triggers link.delete webhook event with the full link data that was deleted.

Smart Routing (Targeting)

The targeting field lets you redirect users to different destinations based on their device type or geographic location. Pass it as a JSON object when creating or updating a link.

Targeting Object Example
{
  "targeting": {
    "ios": "https://apps.apple.com/app/your-app",
    "android": "https://play.google.com/store/apps/details?id=your.app",
    "desktop": "https://yoursite.com/desktop-landing",
    "geo": {
      "US": "https://yoursite.com/us",
      "DE": "https://yoursite.com/de",
      "JP": "https://yoursite.com/jp"
    }
  }
}

Device targeting uses the ios, android, and desktop keys. Geo targeting uses ISO 3166-1 alpha-2 country codes. If no rule matches, the default url is used.

Link Rotation

The rotation field turns one short link into a weighted split across up to 12 destinations. It is available on Pro and Lifetime; a free account gets 403 with code: "UPGRADE_REQUIRED" and premiumFields: ["rotation"].

Destination Object

ParameterTypeRequiredDescription
idstringNoStable identifier, recorded as the click variant on each click. The dashboard groups clicks by it in the "Destination Performance" panel on that link's analytics page; the API returns no ready-made breakdown. Omit on a new destination and Flyn mints one. Reuse the existing id when editing, see Editing a rotation below.
urlstringYesDestination URL for this entry. Checked with Google Safe Browsing, and rejected if it resolves to a private address.
weightnumberYesRelative share of traffic, not a percentage. A weight of 0 keeps the destination in the list without ever serving it.
labelstringNoHuman-readable name for the destination. Cosmetic, it does not affect routing.
Request Example
curl -X POST https://www.flyn.to/api/links \
  -H "Authorization: Bearer flyn_sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/offer-a",
    "rotation": [
      { "url": "https://example.com/offer-a", "weight": 3, "label": "Offer A" },
      { "url": "https://example.com/offer-b", "weight": 1, "label": "Offer B" }
    ]
  }'

The response echoes the stored rotation with an id filled in on every destination:

Stored Rotation
"rotation": [
  { "id": "V1StGXR8", "url": "https://example.com/offer-a", "weight": 3, "label": "Offer A" },
  { "id": "Z_Rk7bQ2", "url": "https://example.com/offer-b", "weight": 1, "label": "Offer B" }
]

How the split works

  • Weights are relative, not percentages. They are normalised across the list, so 3 and 1 send roughly three quarters of clicks to the first destination, and equal weights give an even split.
  • The destination is drawn per click in proportion to its weight, so the split settles over volume rather than following a strict repeating order.
  • Maximum 12 destinations. Sending more returns a 400 rather than silently keeping the first twelve.
  • If every weight is 0 there is nothing left to serve, so no rotation is stored and the link keeps serving the primary url.
  • Bots and crawlers are never sampled into a rotation: they always receive the primary url, which is why it stays required.
  • A matching targeting rule wins. Rotation only applies to visitors that smart routing did not already place.
  • Every destination is safety scanned before the link is saved. A blocked one returns 422 with code: "DESTINATION_BLOCKED".

Editing a rotation

Each destination's id is what its clicks are recorded against, so it is the join key between a destination and its history. A PATCH replaces the whole list: read the link first, send every destination you are keeping back with the id it already has, and leave the id off only for genuinely new ones. Changing an id detaches that destination's past clicks, and reusing one merges two destinations' stats. Never derive ids from list position. Send an empty array to stop rotating and serve the single url again.

Add a destination, keep the existing ids
curl -X PATCH https://www.flyn.to/api/links/a1b2c3d4-... \
  -H "Authorization: Bearer flyn_sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "rotation": [
      { "id": "V1StGXR8", "url": "https://example.com/offer-a", "weight": 3 },
      { "id": "Z_Rk7bQ2", "url": "https://example.com/offer-b", "weight": 1 },
      { "url": "https://example.com/offer-c", "weight": 1 }
    ]
  }'

A/B fields (compatibility view)

abUrlB and abSplit are the older two-destination form of the same idea, where abSplit is the percentage of traffic kept on the primary url. They remain accepted on create and update and are still returned on every link, so existing integrations keep working unchanged. Links that already used them were migrated into rotation under the ids A and B, so their click history stayed attached. When a link carries both, rotation is what the redirect serves. New integrations should use rotation: it is the full picture, and it is the only one of the two that goes past two destinations.

Slug Rules

  • Allowed characters: a-z A-Z 0-9 - _
  • Maximum length: 100 characters
  • Must be unique per domain (you can reuse slugs across different domains)
  • Reserved slugs (e.g. api, login, docs) cannot be used
  • If omitted, a random 6-character slug is generated using nanoid

SDK Examples

TypeScript, Create a Link
const link = await flyn.links.create({
  url: 'https://example.com/my-long-url',
  slug: 'my-campaign',
  domain: 'go.yourcompany.com',
  tags: ['marketing', 'q1-2026'],
  ogTitle: 'Check out our product',
});

console.log(link.shortUrl);
// → https://go.yourcompany.com/my-campaign
TypeScript, List Links with Filters
async function getUrlWithClicks() {
  const res = await flyn.links.list({
    status: 'active',
    tag: 'marketing',
    sort: 'clicks',
    order: 'desc',
    limit: 20,
  });

  res.data.forEach(l => console.log(l.shortUrl, l.clicks));
}

getUrlWithClicks();

Was this page helpful? Spotted something wrong?