Skip to content

Quickstart

301.so turns a URL into a short link that opens the native app when someone taps it from an Instagram or TikTok bio, instead of trapping them in the in-app browser. It also counts the clicks, by channel.

The whole service is one Cloudflare Worker on https://301.so. There are three surfaces: the redirect itself (public), the API (Bearer token), and a debug endpoint (public, on purpose — see Debugging).

Terminal window
curl -s -X POST https://301.so/api/links \
-H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-A "my-client/1.0" \
-d '{
"url": "https://www.youtube.com/@VincentLeSerpent",
"slug": "yt-ig",
"title": "YouTube from Instagram",
"utm": { "utm_source": "instagram", "utm_medium": "bio" }
}'
{
"id": "lnk_aKUzWaeoKA64FmK6",
"shortUrl": "https://301.so/yt-ig",
"hostname": "301.so",
"slug": "yt-ig",
"url": "https://www.youtube.com/@VincentLeSerpent",
"title": "YouTube from Instagram",
"note": null,
"status": 302,
"deeplink": null,
"utm": { "utm_source": "instagram", "utm_medium": "bio" },
"expiresAt": null,
"archivedAt": null,
"createdAt": 1787997074006,
"updatedAt": 1787997074006
}

Leave slug out and you get a random 7-character one. deeplink is null here and the link still opens the YouTube app: the recipe is matched from the destination URL, not from configuration. That field only exists to override or disable it — see Deep links.

Terminal window
curl -sI https://301.so/yt-ig \
-A "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 Chrome/126.0 Mobile Safari/537.36"
HTTP/2 302
location: intent://www.youtube.com/@VincentLeSerpent?utm_source=instagram&utm_medium=bio;#Intent;scheme=https;package=com.google.android.youtube;S.browser_fallback_url=…;end

The UTM parameters configured on the link were merged into the destination, and Android got an intent:// URL that opens the app — falling back to the web page by itself if YouTube isn’t installed.

Terminal window
curl -s "https://301.so/api/stats?days=7" \
-H "Authorization: Bearer $TOKEN" -A "my-client/1.0"
{
"depuis": "2026-08-22",
"jours": 7,
"total": 12,
"parLien": [
{ "shortUrl": "https://301.so/yt-ig", "slug": "yt-ig",
"titre": "YouTube from Instagram", "clics": 8, "bots": 5 }
],
"parJour": [{ "jour": "2026-08-27", "clics": 3, "bots": 1 }]
}

That count comes from D1 and is readable immediately. The dimensions of a click — country, device, in-app browser, which branch the worker took — live in Analytics Engine instead, and are read from a laptop rather than through this API. Stats explains why there are two sources.

  • Authentication — getting a token, and the 1010 trap.
  • API reference — the six endpoints, their bodies and their errors.
  • Deep links — the six recipes and the decision matrix.
  • Limits — the one free-tier ceiling that shapes the design.