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).
1. Create a link
Section titled “1. Create a link”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.
2. Click it
Section titled “2. Click it”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 302location: intent://www.youtube.com/@VincentLeSerpent?utm_source=instagram&utm_medium=bio;#Intent;scheme=https;package=com.google.android.youtube;S.browser_fallback_url=…;endThe 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.
3. Read the clicks
Section titled “3. Read the clicks”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.
Where to go next
Section titled “Where to go next”- 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.