Limen

Build on Limen

Limen API

Let your own programs talk to Limen. A script, website or Discord bot can create links, read your stats and get told the moment someone unlocks.

Paste the OpenAPI link into Postman, or into ChatGPT or Claude, and ask it to write your bot.

limen — Create a linklive

$

How it works

Your appbot, site, script
POST /api/v1/links · 201
LimenAPI + lockers
visitor unlocks
Your serveror Discord
  1. You create an API key in Settings → API. It's like a password for your programs. A read-only key can look but not change anything — handy for a bot that only shows stats.
  2. Your program sends a request to a Limen address (an “endpoint”) with the key attached.
  3. Limen answers in JSON — a simple text format programs can read.
  4. When something happens (an unlock, a sale), Limen can call your address: a webhook.

Keep keys secret. Use them only on a server or in a bot — never inside a public website's code, or anyone could see and use your key. If a key leaks, revoke it in Settings and make a new one.

Your first request

Every request needs this header (replace the example key with yours):

Authorization: Bearer lmn_your_key_here

Create a link:

curl -X POST https://limen.ad/api/v1/links \
  -H "Authorization: Bearer lmn_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"title": "My texture pack", "url": "https://example.com/pack.zip"}'

Limen answers with your new link:

{
  "data": {
    "id": "…",
    "slug": "k7m2qpx",
    "title": "My texture pack",
    "destination_url": "https://example.com/pack.zip",
    "steps": 3,
    "url": "https://limen.ad/s/k7m2qpx",
    "premium_url": "https://limen.ad/p/k7m2qpx",
    "stats": { "views": 0, "unlocks": 0, "premium_unlocks": 0 },
    "status": "open",
    "release_at": null,
    "expires_at": null,
    "max_unlocks": null,
    "remaining_unlocks": null,
    "security_check": null,
    "created_at": "2026-09-17T12:00:00.000Z"
  }
}

Endpoints

POST/api/v1/links

Create a link. Send JSON with:

  • title — required, up to 80 characters
  • url — required, the real destination (https://…)
  • slug — optional custom name, 3–32 letters, numbers or dashes
  • steps — optional, 3 to 5 (default 3; lower values are raised to 3)

GET/api/v1/links?limit=20&offset=0

List your links, newest first. limit is up to 100; use offset to get the next page.

GET/api/v1/links/{slug}

Get one link with its views, unlocks and Premium unlocks.

PATCH/api/v1/links/{slug}

Change a link without losing its stats or its address. Send only what you want to change: title, url, steps or slug. Handy when a download link dies.

DELETE/api/v1/links/{slug}

Delete a link. It stops working immediately.

GET/api/v1/stats?days=28

Totals for your account over 7, 28 or 90 days: views, unlocks, Premium unlocks, checkouts, Premium earnings, ad earnings and your balance. Money is in cents (999 = €9.99).

GET/api/v1/webhooks

Your webhook addresses, including whether the last message got through.

POST/api/v1/webhooks

Add an address: url (https) and optionally events. The reply contains a secret, shown only once.

DELETE/api/v1/webhooks/{id}

Stop sending to that address.

The full description of every field is in the OpenAPI file.

Webhooks: get told when something happens

Instead of asking us every minute, let us tell you. Add an address in Settings → Notifications (or with the API) and we send it a message the moment one of these happens:

  • link.unlocked — someone finished the steps
  • link.premium_unlocked — a Premium member opened it
  • premium.sale — you earned from a Premium payment

Using Discord? Paste a channel webhook address (Server Settings → Integrations → Webhooks) and we post a ready-made message in that channel. Nothing to build. Press Send test next to any address in Settings to see a sample message arrive.

Each message looks like this:

POST https://your-server.com/limen
X-Limen-Event: link.unlocked
X-Limen-Signature: t=1758140000000,v1=9f86d0…

{
  "id": "evt_abc123",
  "type": "link.unlocked",
  "created_at": "2026-09-17T20:08:37.005Z",
  "data": {
    "link": { "id": "…", "slug": "mod-pack", "title": "Ultimate mod pack" },
    "stats": { "views": 132, "unlocks": 44, "premium_unlocks": 7 }
  }
}

Check the signature so nobody can fake it. Take the part after t=, join it to the raw body with a dot, and hash it with your secret:

import { createHmac, timingSafeEqual } from "node:crypto";

const [t, v1] = req.headers["x-limen-signature"].split(",").map((p) => p.split("=")[1]);
const expected = createHmac("sha256", process.env.LIMEN_WEBHOOK_SECRET)
  .update(`${t}.${rawBody}`)
  .digest("hex");

const valid =
  timingSafeEqual(Buffer.from(expected), Buffer.from(v1)) &&
  Date.now() - Number(t) < 5 * 60 * 1000; // ignore old messages

Answer with any 2xx status within 5 seconds. We try once more after a failure; if an address keeps failing it is switched off, and you can turn it back on in Settings.

Example: a Discord bot command

A /lock command that turns any link into a Limen link:

// discord.js — a /lock command that turns any link into a Limen link
client.on("interactionCreate", async (interaction) => {
  if (!interaction.isChatInputCommand() || interaction.commandName !== "lock") return;
  const link = interaction.options.getString("url", true);

  const res = await fetch("https://limen.ad/api/v1/links", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.LIMEN_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ title: "Server resource pack", url: link }),
  });
  const { data, error } = await res.json();
  if (error) return interaction.reply({ content: error.message, ephemeral: true });
  await interaction.reply(`Here's your link: ${data.url}`);
});

Try it with your key

Paste a key from Settings → API and watch a real answer come back. A read-only key is enough.

The request goes straight from your browser to Limen. Your key isn't stored anywhere — only use this on your own device.

Errors and limits

When something goes wrong you get an error code and a message you can show to people:

{ "error": { "code": "validation_error", "message": "Enter a full link starting with https://" } }
  • 401 — missing, wrong or revoked API key
  • 403 — a read-only key tried to change something
  • 404 — that link isn't in your account
  • 409 — the custom name is already taken
  • 422 — something in your data isn't valid (the message says what)
  • 429 — too many requests: the limit is 60 per minute per key

Every reply tells you where you stand: X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (when the minute resets). A 429 also has Retry-After in seconds.

Versions and changes

Our promise: nothing in /api/v1 is removed or changed in a way that breaks your code without 90 days' notice by email to every account with an API key. New fields may be added at any time — ignore what you don't know.

  • 19 Sep 2026Limited drops: release_at, expires_at and max_unlocks on create and PATCH; status and remaining_unlocks on every link. security_check shows the destination's VirusTotal result.
  • 18 Sep 2026PATCH /links/{slug} to edit links; webhooks; read-only keys; rate-limit headers on every reply.
  • 17 Sep 2026v1 launched: create, list, read and delete links; account stats.