Build on Linktain.
Three endpoints. Make a locked link, list what you have, read what each one earned. JSON goes in, JSON comes out, and one bearer key opens all of it.
Make a key in Settings, paste it into this, and you have a live locked link in one request. Everything else on this page is detail.
curl -X POST https://linktain.com/api/v1/links \
-H "Authorization: Bearer lt_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"title":"My download","destinationUrl":"https://example.com/file"}'The shortUrl that comes back is the thing you share. It starts counting the moment somebody opens it.
One header on every request. Keys begin with lt_ and are made in Dashboard, Settings.
Authorization: Bearer lt_your_api_key_here
We store a hash, never the key itself. Lose it and there is nothing to look up: revoke it and make another.
The next request with a revoked key gets a 401. There is no grace period and nothing is cached.
A key can see and change everything you can. Anything done with it is done by you, so keep it off the client side.
A few requests a second and you will never hear from us. We would rather tell you the truth than invent a number.
Every amount in this API is an integer number of mills. A mill is a thousandth of a dollar, so 1000 mills is $1.00 and 6420 mills is $6.42.
Integers, because money in a float is money that eventually does not add up. Divide by 1000 at the moment you print it and never before.
const usd = (mills) => (mills / 1000).toFixed(2); usd(6420); // "6.42"
Recent days can still move. A partner reversing a conversion takes it back off the balance it landed on, sometimes weeks later, so treat the last few days as provisional rather than final.
Wrap any URL in a gate and get a short link back.
The link is live the moment this returns. Share the shortUrl and it starts counting views.
Choose accessMode for tasks only, tasks with optional Premium, or Premium only. Omit it to keep the default: tasks with optional Premium. You can change each link in the dashboard.
titlestringrequired- Link title shown in your dashboard and on the visitor’s locker page. 1 to 120 characters.
destinationUrlstringrequired- Where the visitor lands once the gate is finished. Must start with http:// or https://. Up to 2000 characters.
accessModestring- tasks, tasks_or_premium (default), or premium. Takes precedence over legacy premiumOnly.
premiumOnlyboolean- Legacy field. True requires a pass; false keeps optional Premium. Prefer accessMode.
curl -X POST https://linktain.com/api/v1/links \
-H "Authorization: Bearer lt_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"title":"My download","destinationUrl":"https://example.com/file"}'{
"id": "cmd7x1abc0001xyz",
"slug": "x7k2m9a",
"shortUrl": "https://linktain.com/l/x7k2m9a",
"title": "My download",
"destinationUrl": "https://example.com/file",
"accessMode": "tasks_or_premium",
"premiumOnly": false,
"createdAt": "2026-07-27T12:00:00.000Z"
}idstring- Use it for the stats endpoint.
slugstring- The short code on the end of the URL.
shortUrlstring- The link to share. This is the one you want.
titlestring- What you sent.
destinationUrlstring- What you sent.
accessModestring- The saved access mode.
premiumOnlyboolean- True when a Premium pass is required.
createdAtstring- ISO 8601, UTC.
Every link on your account, newest first, with its lifetime totals.
Returns all your links in one response. Pagination is not currently supported.
curl https://linktain.com/api/v1/links \ -H "Authorization: Bearer lt_your_api_key_here"
{
"links": [
{
"id": "cmd7x1abc0001xyz",
"slug": "x7k2m9a",
"shortUrl": "https://linktain.com/l/x7k2m9a",
"title": "My download",
"destinationUrl": "https://example.com/file",
"active": true,
"views": 1523,
"unlocks": 1284,
"earnedMills": 7420,
"createdAt": "2026-07-27T12:00:00.000Z"
}
]
}linksarray- The list. Newest first.
links[].idstring- Pass this to the stats endpoint.
links[].slugstring- The short code.
links[].shortUrlstring- The full link to share.
links[].titlestring- Your name for it.
links[].destinationUrlstring- Where it sends people.
links[].activeboolean- False if you paused it or we disabled it. A disabled link serves nothing.
links[].accessModestring- tasks, tasks_or_premium, or premium.
links[].viewsinteger- Times the gate was opened, lifetime.
links[].unlocksinteger- Times a visitor finished the gate, lifetime.
links[].earnedMillsinteger- Recorded link earnings, including unlock credits, conversions and attributed Premium payments, in mills.
links[].createdAtstring- ISO 8601, UTC.
Lifetime totals, the earnings split, and a day by day series.
Ask for a link that is not yours and you get a 404, the same answer as a link that does not exist. We do not confirm that somebody else's id is real.
unlocks counts recorded task unlocks, including repeat visits. The legacy paidUnlocks field counts unlocks eligible for a per-unlock credit after the 24-hour visitor deduplication. A counted unlock can earn zero; use earnedMills for revenue.
curl https://linktain.com/api/v1/links/cmd7x1abc0001xyz/stats \ -H "Authorization: Bearer lt_your_api_key_here"
{
"views": 1523,
"unlocks": 1284,
"paidUnlocks": 1180,
"conversions": 47,
"unlockMills": 1180,
"offerMills": 5240,
"passMills": 1000,
"earnedMills": 7420,
"last14d": [
{ "date": "2026-07-14", "views": 96, "unlocks": 80 },
{ "date": "2026-07-15", "views": 112, "unlocks": 95 }
]
}viewsinteger- Times the gate was opened.
unlocksinteger- Times a visitor finished the gate.
paidUnlocksinteger- Deduplicated task unlocks. This legacy name does not guarantee a payment.
conversionsinteger- Recorded conversion events, including reversals.
unlockMillsinteger- Your share from the flat per-unlock rate, in mills.
offerMillsinteger- Recorded conversion earnings, net of reversals, in mills.
passMillsinteger- Premium earnings attributed to this link, including initial payments and renewals, in mills.
earnedMillsinteger- unlockMills + offerMills + passMills. Excludes referral bonuses and payouts.
last14darray- Fourteen UTC days, oldest first, zero filled. Days with nothing still appear.
last14d[].datestring- UTC calendar date, YYYY-MM-DD.
last14d[].viewsinteger- Gate opens that day.
last14d[].unlocksinteger- Finished gates that day.
One tag on a site you own turns every outbound link on it into a locker. You do not rewrite any hrefs and you do not create the links: the first time somebody clicks one we make it, and every click after that uses the same one.
<script src="https://linktain.com/e.js" data-key="pk_your_site_key"></script>
Register the site in Dashboard, Links to get the key. It is public on purpose, because it sits in your page source where anybody can read it. What stops somebody else using it is the hostname you registered against it: a request from any other site is refused.
Links to your own pages, anchors, mailto: and tel:, downloads, middle clicks and ctrl-clicks.
If we are slow or down, the click goes straight through to where it was already going. Your site does not break because ours did.
<a href="https://example.com/sponsor" data-linktain="off">never locked</a>
<script src="https://linktain.com/e.js"
data-key="pk_your_site_key"
data-selector="a.paid"></script>Every failure is the same shape, { "error": "..." }, with the status carrying the category and the string saying what to fix.
- 400
- The body was not JSON, or a field failed validation.The message names the field, e.g. "destinationUrl: must use http:// or https://". Fix it and send again.
- 401
- No Authorization header, or the key is wrong, or you revoked it.Check the header reads Bearer lt_… and that the key is still listed in Settings.
- 403
- The account cannot use the API: awaiting approval, suspended, or banned.The message says which. Approval is the usual one and it is a person, not a queue you can retry past.
- 404
- No link with that id on your account.Check the id came from your own list. Somebody else's id answers 404 too.
- 500
- We broke.Retry once. If it keeps happening, tell us on Telegram and include roughly when.
- 503
- Could not allocate a short code, which takes an unlucky run of collisions.Retry. It is transient and it clears immediately.
Get a key and make the first call.
Keys live in Settings. If something here is wrong or missing, say so on Telegram at @LinksWithAndrew and it gets fixed, usually the same day.
Create an API key