URL Shortener API Tutorial: Automate Link Creation with Python and JavaScript

Hands-on tutorial for using Lnky's REST API to create, update and analyze short URLs programmatically — with code samples in Python, Node.js and curl.

By Tomas Aldea June 12, 2026 6 min read
url-shortener-api-tutorial hero illustration

If you're generating short URLs by hand in a dashboard, you're doing it wrong past about 20 links a month. The right move is to script it: a single API call creates the link, attaches metadata, and returns the short URL ready to ship into whatever workflow needs it.

This tutorial walks through the most common patterns using Lnky's REST API, with examples in Python, Node.js and curl.

Authentication

Lnky uses bearer token authentication. Generate a token from Settings → API Token → Generate. Store it as an environment variable — never commit it to a repo.

export LNKY_TOKEN="your-token-here"

Every request includes the token in an Authorization header:

Authorization: Bearer your-token-here

Endpoint summary

GET    /api/shorten              List your short URLs (returns a raw JSON array)
POST   /api/shorten              Create a new short URL
PATCH  /api/shorten/{id}         Update an existing short URL
DELETE /api/shorten/{id}         Delete a short URL (204 on success)

All endpoints return JSON. Standard HTTP status codes apply (200, 201, 204, 400, 401, 403, 404, 422, 429).

The create/update endpoints accept a small set of fields: longUrl, password, expires_at (ISO 8601, must be in the future) and max_clicks. Branding, custom slugs, custom OG tags, tags and retargeting pixels are dashboard-only today.

Create a short URL

curl

curl -X POST https://lnky.click/api/shorten \
  -H "Authorization: Bearer $LNKY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "longUrl": "https://example.com/very/long/path?with=params",
    "expires_at": "2026-12-31T23:59:59Z",
    "max_clicks": 1000
  }'

Response:

{
  "short_url": "https://lnky.click/aZb9Kp"
}

Python (requests)

import os
import requests

API_URL = "https://lnky.click/api/shorten"
TOKEN = os.environ["LNKY_TOKEN"]

def shorten(long_url, **kwargs):
    payload = {"longUrl": long_url, **kwargs}
    r = requests.post(
        API_URL,
        json=payload,
        headers={"Authorization": f"Bearer {TOKEN}"},
        timeout=10,
    )
    r.raise_for_status()
    return r.json()["short_url"]

# Usage
short = shorten(
    "https://example.com/landing?utm_source=email",
    expires_at="2026-12-31T23:59:59Z",
)
print(short)

Node.js (fetch)

const API_URL = "https://lnky.click/api/shorten";
const TOKEN = process.env.LNKY_TOKEN;

async function shorten(longUrl, opts = {}) {
  const r = await fetch(API_URL, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ longUrl, ...opts }),
  });
  if (!r.ok) throw new Error(`shorten failed: ${r.status}`);
  const data = await r.json();
  return data.short_url;
}

// Usage
const short = await shorten(
  "https://example.com/landing?utm_source=email",
  { expires_at: "2026-12-31T23:59:59Z" }
);
console.log(short);

Common patterns

Pattern 1 — bulk-create campaign URLs

You have 50 product SKUs and want one branded short URL per product for a print campaign.

import csv
import os

API_URL = "https://lnky.click/api/shorten"
TOKEN = os.environ["LNKY_TOKEN"]

with open("products.csv") as f, open("short_urls.csv", "w") as out:
    reader = csv.DictReader(f)
    writer = csv.DictWriter(out, fieldnames=["sku", "destination", "short_url"])
    writer.writeheader()
    for row in reader:
        destination = (
            f"https://acme.com/products/{row['sku']}"
            f"?utm_source=print&utm_campaign=q4&utm_content={row['sku']}"
        )
        short = shorten(destination)
        writer.writerow({"sku": row["sku"], "destination": destination, "short_url": short})

Note: rate-limited to 60 requests per minute. For 1,000+ links, add a time.sleep(1) between calls or batch over multiple minutes.

Pattern 2 — list and update

Maybe you need to migrate all your short URLs to point at a new destination domain (old.comnew.com).

r = requests.get(API_URL, headers={"Authorization": f"Bearer {TOKEN}"})
links = r.json()  # GET /api/shorten returns a raw JSON array, not {data: [...]}

for link in links:
    if "old.com" in link["original_url"]:
        new_url = link["original_url"].replace("old.com", "new.com")
        requests.patch(
            f"{API_URL}/{link['id']}",
            json={"longUrl": new_url},
            headers={"Authorization": f"Bearer {TOKEN}"},
        )
        print(f"Updated {link['id']}: -> {new_url}")

The short URL stays the same; only the destination changes. Live links update immediately.

Pattern 3 — programmatic links from a publishing workflow

A common SaaS pattern: every time you publish a blog post, automatically create a short URL for it (for Twitter sharing) with the post-specific UTM parameters baked in.

// In a publishing webhook handler:
async function onPostPublished(post) {
  const destination = `${post.url}?utm_source=twitter&utm_medium=social&utm_campaign=blog-launch`;
  const shortUrl = await shorten(destination);
  await db.updatePost(post.id, { twitter_short_url: shortUrl });
}

Now your "share to Twitter" button uses the short URL with attribution baked in.

Pattern 4 — webhooks for click events (coming soon)

Lnky is working on webhook delivery for click events. Track this on the roadmap; today the recommended approach is to poll the API for click counts.

Rate limits

  • POST /api/shorten: 60 requests per minute per token.
  • All other endpoints: standard Laravel rate limit (60/min).

A 429 response includes a Retry-After header. Respect it with backoff:

import time
import requests

def shorten_with_retry(long_url):
    for attempt in range(3):
        r = requests.post(API_URL, json={"longUrl": long_url}, headers={"Authorization": f"Bearer {TOKEN}"})
        if r.status_code == 429:
            wait = int(r.headers.get("Retry-After", 5))
            time.sleep(wait)
            continue
        r.raise_for_status()
        return r.json()["short_url"]
    raise RuntimeError("rate limit exhausted")

Error handling

Status Cause Fix
401 Missing or invalid token Regenerate from dashboard
403 Plan limit reached Upgrade or wait for next month
422 Validation error (bad URL, slug taken, etc) Check errors field in response body
429 Rate limited Honor Retry-After, retry with backoff
500 Server error Retry once; report if persistent

Idempotency

The API does not deduplicate on longUrl — calling POST /api/shorten twice with the same URL produces two distinct short URLs. If you need idempotency (e.g. in a retry-prone job queue), store the short URL in your own DB keyed by the long URL.

Security

  • Rotate tokens periodically. If your token leaks (committed to git, posted in a Slack screenshot), regenerate from the dashboard.
  • Use one token per service. If you have a publishing pipeline, a CSV importer and a marketing automation tool all using the API, give each its own token. Revoking one doesn't affect the others.
  • Don't expose the token in client-side code. All API calls should originate from your backend.

What's next

If you're integrating Lnky into a larger system, take a look at the admin panel (if you're on Agency) for usage monitoring across teammates, and the pricing page for the per-tier rate-limit details.

The full API reference is at /api-docs.

Subscribe to the Lnky Blog

Get new posts on link analytics, SEO and short-URL tactics — about one email a month.