Developer guide
Integrate ilync in minutes
From a first API call through QR and barcode generation, authentication, rate limits, link safety, link-in-bio pages, private links, analytics, avatar management, admin endpoints, custom domains, and account deletion.
Overview
ilync turns long URLs into short, shareable codes and redirects visitors to the original destination. Use it from the browser or programmatically over a small JSON API.
- Instant shortening
- Accounts & dashboard
- Optional expiration
- Click analytics
- QR & barcode API
- API keys & rate limits
- Advanced safety screening
- Free public URL checker
- Link in bio pages
- Private links
- Passcode-protected links
- Avatar upload & removal
- Admin link management
- Custom domains
- Light & dark themes
Quick start
ilync is a hosted HTTP service, so there is nothing to install or deploy to start using it. Point your application at the API and you can create short links in minutes. Every endpoint accepts and returns JSON over HTTPS.
1. Get an API key (recommended)
Anonymous requests work out of the box with a lower rate limit. For higher limits, link ownership, and private links, sign in and create a key under Developer API, then send it as a bearer token on each request. Store it as a secret in your backend — never ship it in client-side code.
2. Create your first short link
Send a POST to /api/urls with the URL to shorten. The Authorization header is optional but recommended for production integrations.
curl -X POST https://ilync.dev/api/urls \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ilk_your_key_here' \
-d '{"url":"https://example.com/a/very/long/path"}'const res = await fetch("https://ilync.dev/api/urls", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer ilk_your_key_here", // optional
},
body: JSON.stringify({ url: "https://example.com/a/very/long/path" }),
});
if (!res.ok) throw new Error(`ilync error: ${res.status}`);
const { shortUrl } = await res.json();
console.log(shortUrl); // → https://ilync.dev/Ab3xK9import requests
res = requests.post(
"https://ilync.dev/api/urls",
json={"url": "https://example.com/a/very/long/path"},
headers={"Authorization": "Bearer ilk_your_key_here"}, # optional
timeout=10,
)
res.raise_for_status()
print(res.json()["shortUrl"]) # → https://ilync.dev/Ab3xK93. Use the response
A successful call returns 201 Created with the short link. Persist shortUrl (or shortCode) and hand it to your users — visiting it issues a 307 redirect to the original destination.
{
"shortCode": "Ab3xK9",
"shortUrl": "https://ilync.dev/Ab3xK9",
"originalUrl": "https://example.com/a/very/long/path",
"expiresAt": null,
"createdAt": "2026-07-11T21:50:00.000Z",
"isPrivate": false,
"ownerId": null
}That's the whole loop. From here, see Create a link for every request field, Authentication for keys, and Rate limits to size your usage.
Using the web UI
- Paste a long URL into the form on the home page.
- Optionally set a custom short code and/or pick an expiration.
- Signed in? Add an optional passcode — visitors must enter it before being redirected.
- Submit — the short link appears with Copy, a QR / barcode generator, and a link to stats.
- The Recently shortened list shows recent public links and their click counts; open a link's stats page at
/{code}/stats.
Links created while signed out are public and appear in the landing-page feed. Links you create while signed inare unlisted — hidden from the public feed and tracked in your account's My links dashboard instead (they stay fully redirectable). Expired links are excluded from the public feed automatically.
To add or remove a passcode on an existing link, open it in your account's My links dashboard and use the Edit panel (see Passcodes).
Accounts & the dashboard
Accounts support Google and email/password sign-in. Visit /account to sign in or create one. Signing in is required to save links and to mint API keys. Email/password accounts receive a verification link on sign-up — click it to confirm your address (you can re-send it any time from the dashboard).
| Dashboard area | What it does |
|---|---|
| Profile | Avatar (uploadable/removable), editable display name, sign-in method, and a copyable user id. |
| Stats | Total links, active links, and links expiring in the next 7 days. |
| Email verification | Email/password accounts get a verification link on sign-up. The dashboard banner lets you re-send it or confirm once you've clicked it; your address shows a Verified badge. |
| Link in bio | Shortcut to manage your public /@username page — see Link in bio. |
| My links | Every link you created while signed in — open, copy, view stats, or remove (removing only stops tracking; the short link keeps working). Paginated and filterable. |
| Developer API | Create, list, and revoke API keys. This is the easiest way to manage keys (see API keys below). |
| Custom domains | Register up to 3 custom domains with DNS-based verification. |
| Danger zone | Permanent account deletion — removes all links, API keys, access grants, and Firestore data. |
Your saved links and profile are private to your account — only you can read or modify them.
Link in bio
Claim a public page at /@username and publish social profiles plus custom buttons — ideal for Instagram, TikTok, and other bios. Edit everything from /link (also linked from your account). Signing in is required to create or update a page.
Public URL
Visitors open /@username. Reserved names and usernames that clash with existing short codes cannot be claimed.
Editor
Set display name, tagline, avatar (uploadable), publish state, socials, and custom buttons on /link.
Social profiles
Instagram, X, TikTok, YouTube, LinkedIn, GitHub, Facebook, Threads, Pinterest, WhatsApp, Telegram, Stack Overflow, Slack, Medium, Discord, Reddit, Twitch, Spotify, email, website, and other — handles or full URLs are accepted.
Custom buttons
Stacked call-to-action links for shops, newsletters, portfolios, or anything else you want to highlight.
HTTP API
Signed-in clients can load and save the page with an ID token (same bearer auth used elsewhere for account actions).
/api/bioBearer requiredReturns your page, or { "bio": null } if you have not created one yet.
curl https://ilync.dev/api/bio \
-H 'Authorization: Bearer <id-token>'{ "bio": null }/api/bioBearer required| Field | Type | Required | Notes |
|---|---|---|---|
username | string | yes | 3–24 chars; letters, numbers, underscores; stored lowercase. |
displayName | string | yes | 1–80 characters. |
tagline | string | null | no | Up to 160 characters. |
avatarUrl | string | null | no | Checked with the same destination safety rules as short links. |
published | boolean | no | Defaults to true. Unpublished pages return 404 publicly. |
links | array | yes | Up to 40 items. Each needs kind (social|custom), label, url; socials need platform. |
curl -X PUT https://ilync.dev/api/bio \
-H 'Authorization: Bearer <id-token>' \
-H 'Content-Type: application/json' \
-d '{
"username": "you",
"displayName": "Your name",
"tagline": "Creator · Builder",
"published": true,
"links": [
{ "kind": "social", "platform": "instagram", "label": "Instagram", "url": "you" },
{ "kind": "custom", "label": "My shop", "url": "https://example.com/shop" }
]
}'/api/bio/analyticsBearer requiredReturns visit analytics for your link-in-bio page, broken down by time period.
curl https://ilync.dev/api/bio/analytics \
-H 'Authorization: Bearer <id-token>'{
"total": 1284,
"periods": {
"24h": 42,
"3d": 118,
"7d": 256,
"30d": 890,
"60d": 1203
}
}Core concepts
| Concept | Summary |
|---|---|
| Short code | A short, unique code (e.g. Ab3xK9) generated for each link — collision-free and non-sequential. |
| Link in bio | A signed-in user's public page at /@username, with socials and custom buttons. Managed at /link. |
| Account | A signed-in user. Required to save links to a dashboard, mint API keys, and manage a link-in-bio page. |
| Public link | Anyone with the code is redirected, and it may appear in the public feed. The default for anonymous shortening. |
| Unlisted link | Redirectable by anyone with the code, but hidden from the public feed. Links created while signed in are unlisted. |
| Private link | Only the owner or granted users may follow it; everyone else gets 401. Requires an API key to create. |
| Passcode-protected link | Anyone may follow it, but visitors must first enter a passcode. The passcode is stored as a salted hash, never in plaintext, and requires an account to set. Applies to redirects in a browser; see Passcodes. |
| API key | An opaque token (ilk_…) that authenticates requests, raises your rate limit, and owns the links it creates. Minted from your account and tied to it. |
| User id | Every API key maps to a numeric userId, used for ownership checks and access grants. |
| Admin | A user whose email or user ID is listed in ADMIN_EMAILS / ADMIN_UIDS. Can manage all links via /admin. |
| Custom domain | A user-registered domain for serving short links. Verified via DNS TXT record. |
Create a short link
/api/urlsAuth optional| Field | Type | Required | Notes |
|---|---|---|---|
url | string | yes | With or without a scheme (defaults to https://). http/https only, must have a dotted domain. Unsafe or disallowed destinations are rejected. |
expiresAt | string | number | no | ISO 8601 string or epoch ms. Must be in the future. |
customCode | string | no | Choose your own short code (3–32 letters/numbers). Alias: shortCode. Requires a signed-in session or API key. Returns 401 if anonymous, 409 if taken. Omit to auto-generate. |
private | boolean | no | true creates a private link. Requires an API key. |
listed | boolean | no | Defaults to true. Set false to keep the link out of the public feed (the signed-in web form does this automatically). Does not affect redirects. |
passcode | string | no | Optional. Visitors must enter this passcode before being redirected. Requires a signed-in session or API key (returns 400 for anonymous callers). At least 4 characters. The passcode is stored only as a salted hash. |
curl -X POST https://ilync.dev/api/urls \
-H 'Content-Type: application/json' \
-d '{"url":"https://example.com/a-very-long-url"}'{
"shortCode": "Ab3xK9",
"shortUrl": "https://ilync.dev/Ab3xK9",
"originalUrl": "https://example.com/a-very-long-url",
"expiresAt": null,
"createdAt": "2026-07-11T17:38:46.646Z",
"isPrivate": false,
"ownerId": null,
"hasPasscode": false
}curl -X POST https://ilync.dev/api/urls \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ilk_your_key_here' \
-d '{"url":"https://example.com/launch","customCode":"launch"}'curl -X POST https://ilync.dev/api/urls \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ilk_your_key_here' \
-d '{"url":"example.com/secret","expiresAt":"2026-12-31T23:59:59Z","private":true}'curl -X POST https://ilync.dev/api/urls \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ilk_your_key_here' \
-d '{"url":"https://example.com/members-only","passcode":"season-pass"}'Follow a short link (redirect)
/{code}Private onlyReturns 307 Temporary Redirect with a Locationheader pointing to the original URL. For private links, send the owner's or a granted user's key. Public redirects are rate-limited to reduce scraping and abuse — over-limit clients receive 429.
Links with a passcode return an HTML page asking for the passcode instead of an immediate redirect. Entering it sets a short-lived cookie and then redirects — see Passcodes.
curl -i https://ilync.dev/Ab3xK9 \
-H 'Authorization: Bearer ilk_your_key_here'Delete a short link
/api/urls/{code}Ownercurl -X DELETE https://ilync.dev/api/urls/Ab3xK9 \
-H 'Authorization: Bearer ilk_your_key_here'Grant access to a private link
/api/urls/{code}/accessOwnerGrants another user (by their userId) permission to follow a private link.
curl -X POST https://ilync.dev/api/urls/Ab3xK9/access \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ilk_owner_key_here' \
-d '{"userId":2}'Link analytics
/api/analytics/{code}Auth noneReturns click totals and breakdowns. Recording happens off the redirect hot path, so it never slows a redirect. A human-friendly page also lives at /{code}/stats.
curl https://ilync.dev/api/analytics/Ab3xK9{
"shortCode": "Ab3xK9",
"originalUrl": "https://example.com/…",
"clicks": 4,
"byPlatform": [
{ "platform": "iOS", "count": 1 },
{ "platform": "Windows", "count": 1 }
],
"recent": [
{ "createdAt": "…", "referrer": "https://twitter.com/", "platform": "iOS" }
]
}QR codes & barcodes
Generate a QR code or 1D barcode from any value — a short link, a product SKU, a serial number. Auth is optional. CORS is open, so you can call this from a browser app or drop the GET URL into an <img>. A visual playground lives at /code.
/api/codesAuth optional/api/codesAuth optional| Field | Type | Required | Notes |
|---|---|---|---|
value | string | yes | Text to encode. Max 2048 characters. Query param on GET, JSON field on POST. |
format | string | no | QR (default), CODE128, CODE39, EAN13, UPC-A, or ITF14. Aliases like qrcode, c128, upca are accepted. |
output | string | no | png (default, image/png), svg (image/svg+xml), or json (base64 PNG plus metadata). |
size | integer | no | 64–2048. Defaults to 256 on GET (for embeds) and 512 on POST. |
foreground | string | no | Hex color for modules/bars. Alias: fg. Default #18181b. |
background | string | no | Hex color for the quiet zone. Alias: bg. Default #ffffff. |
errorCorrection | string | no | QR only: L, M (default), Q, or H. Alias: ec. Ignored for 1D barcodes. |
download | boolean | no | If true (or 1), sets Content-Disposition: attachment so the browser downloads the file. |
logo | string | no | QR only. An image URL or a data:image base64 URI to place in the center of the code. 2 MB limit, rounded and padded to match the web UI. Best sent as JSON on POST (data URIs can get mangled in a GET query string). |
| Format | Encodes |
|---|---|
| QR | Any text or URL |
| CODE128 | Nearly any ASCII text — URLs, SKUs, serials |
| CODE39 | Uppercase letters, digits, and - . $ / + % space |
| EAN13 | 12–13 digits (retail) |
| UPC-A | 11–12 digits (North American retail) |
| ITF14 | Exactly 14 digits (shipping cartons) |
Embed as an image
GET returns the image bytes. Use it as an src — no SDK, no key required for modest volume. Anonymous GET responses are cacheable.
<img
src="https://ilync.dev/api/codes?value=https%3A%2F%2Filync.dev%2FAb3xK9&format=QR&size=256"
alt="QR code for https://ilync.dev/Ab3xK9"
width="256"
height="256"
/>curl "https://ilync.dev/api/codes?value=https%3A%2F%2Filync.dev%2FAb3xK9&format=QR&size=512" \
-o qr.pngcurl "https://ilync.dev/api/codes?value=HELLO-42&format=CODE128&output=svg&fg=%2318181b&bg=%23ffffff" \
-o barcode.svgPOST from an app
POST the same fields as JSON. Send an API key for the higher quota. output=json is the easiest path when you cannot handle raw image bytes.
curl -X POST https://ilync.dev/api/codes \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ilk_your_key_here' \
-d '{"value":"https://ilync.dev/Ab3xK9","format":"QR","size":512}' \
-o qr.pngcurl -X POST https://ilync.dev/api/codes \
-H 'Content-Type: application/json' \
-d '{"value":"5901234123457","format":"EAN13","output":"json"}'{
"format": "EAN13",
"value": "5901234123457",
"output": "png",
"mimeType": "image/png",
"size": 512,
"data": "iVBORw0KGgoAAAANSUhEUgAA..."
}const res = await fetch("https://ilync.dev/api/codes", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "ilk_your_key_here",
},
body: JSON.stringify({ value: "https://ilync.dev/Ab3xK9", format: "QR" }),
});
const blob = await res.blob();
const url = URL.createObjectURL(blob);Add a logo
Pass logo to sit a brand mark in the center of a QR code. It accepts a public image URL or a data:image/...;base64, URI, up to 2 MB. The result is rounded and given a quiet-zone backing, matching the /code playground. Use errorCorrection=H so the code still scans. Because + and / in a data URI can be mangled in a query string, send data URIs as JSON on POST.
curl -X POST https://ilync.dev/api/codes \
-H 'Content-Type: application/json' \
-d '{"value":"https://ilync.dev/Ab3xK9","format":"QR","size":512,"output":"png","errorCorrection":"H","logo":"https://cdn.example.com/logo.png"}' \
-o qr-with-logo.pngcurl -X POST https://ilync.dev/api/codes \
-H 'Content-Type: application/json' \
-d '{"value":"https://ilync.dev/Ab3xK9","format":"QR","errorCorrection":"H","logo":"data:image/png;base64,iVBORw0KGgo..."}' \
-o qr-with-logo.pngMint an API key
/api/keysAccountYou must have an account. Create a key from the account dashboard (easiest), or call the endpoint directly with your account ID token as a bearer token. The plaintext key is returned once (only its hash is stored) plus your userId.
curl -X POST https://ilync.dev/api/keys \
-H 'Authorization: Bearer <YOUR_ID_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"label":"my-app"}'{ "apiKey": "ilk_6I2kaePfLIiEUUvVHHYMaBDzPre4npf4", "label": "my-app", "userId": 1 }List your keys with GET /api/keys:
curl https://ilync.dev/api/keys \
-H 'Authorization: Bearer <YOUR_ID_TOKEN>'{
"keys": [
{ "id": 1, "label": "my-app", "prefix": "ilk_6I2k", "createdAt": "2026-07-11T21:50:00.000Z" }
]
}And revoke one with DELETE /api/keys/{id} (same bearer token):
curl -X DELETE https://ilync.dev/api/keys/1 \
-H 'Authorization: Bearer <YOUR_ID_TOKEN>'Clean up expired links
/api/cleanupSecret / cronActively deletes every expired link (expired links are also removed lazily when someone tries to follow them, and are hidden from the public feed immediately). Pass CLEANUP_SECRETas a bearer token when set; when unset, it's reachable only outside production. Trigger it on a schedule.
curl -X POST https://ilync.dev/api/cleanup \
-H 'Authorization: Bearer <CLEANUP_SECRET>'Sign in (programmatic)
/api/auth/sign-inNoneExchanges email + password for an ID token. Intended for headless / programmatic access — scripts, CLI, server-to-server. The web UI uses the client SDK directly.
curl -X POST https://ilync.dev/api/auth/sign-in \
-H 'Content-Type: application/json' \
-d '{"email":"user@example.com","password":"your-password"}'{ "idToken": "eyJ…", "expiresIn": "3600" }Use the returned idToken as Authorization: Bearer <idToken> on any endpoint that accepts authentication (e.g. POST /api/keys, PUT /api/bio). The token expires after expiresIn seconds (typically 3600).
Avatar management
Upload, serve, and remove profile photos. Avatars are stored in Vercel Blob under avatars/{uid}/ and served through a proxy endpoint with caching.
/api/upload/avatarAccountUpload a profile photo as multipart/form-data with a file field. Max 4 MB; allowed types: JPEG, PNG, WebP, GIF.
curl -X POST https://ilync.dev/api/upload/avatar \
-H 'Authorization: Bearer <ID_TOKEN>' \
-F 'file=@photo.jpg'{
"url": "/api/avatar?pathname=avatars%2Fabc123%2F1700000000.jpg",
"blobUrl": "https://..."
}/api/avatar?pathname=avatars/{uid}/{file}NoneServes an avatar image with the correct Content-Type header and a 24-hour cache directive. No authentication required.
curl "https://ilync.dev/api/avatar?pathname=avatars%2Fabc123%2F1700000000.jpg" \
-o avatar.jpg/api/avatar?pathname=avatars/{uid}/{file}AccountRemoves an avatar from blob storage. The pathname must belong to the requesting user.
curl -X DELETE "https://ilync.dev/api/avatar?pathname=avatars%2Fabc123%2F1700000000.jpg" \
-H 'Authorization: Bearer <ID_TOKEN>'Custom domains
Register custom domains for serving short links. Each account can register up to 3 domains. Verification is done via a DNS TXT record.
/api/domainsAccountList your registered custom domains.
curl https://ilync.dev/api/domains -H 'Authorization: Bearer <ID_TOKEN>'{
"domains": [
{
"id": 1,
"domain": "go.example.com",
"verified": false,
"verificationToken": "abc123...",
"createdAt": "2026-07-20T10:00:00.000Z"
}
]
}/api/domainsAccountRegister a new custom domain.
curl -X POST https://ilync.dev/api/domains \
-H 'Authorization: Bearer <ID_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"domain":"go.example.com"}'{
"domain": {
"id": 1,
"domain": "go.example.com",
"verified": false,
"verificationToken": "abc123...",
"instructions": "Add a TXT record: _ilync.go.example.com with value \"abc123...\""
}
}Account deletion
Permanently delete your account and all associated data. This action is irreversible and removes all links, API keys, access grants, and Firestore data.
/api/accountAccountThe confirm field must be exactly "DELETE_MY_ACCOUNT". The web UI additionally requires typing the word DELETE in a prompt before calling this endpoint.
curl -X DELETE https://ilync.dev/api/account \
-H 'Authorization: Bearer <ID_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"confirm":"DELETE_MY_ACCOUNT"}'{ "deleted": true }Authentication & API keys
- Sign in first. Minting keys requires an account — create one from the account dashboard, then generate keys there. For programmatic sign-in, use POST /api/auth/sign-in.
- Send a key using either
Authorization: Bearer ilk_…orX-API-Key: ilk_…:via Authorization headercurl -X POST https://ilync.dev/api/urls \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ilk_your_key_here' \ -d '{"url":"https://example.com/long"}'same request via X-API-Key headercurl -X POST https://ilync.dev/api/urls \ -H 'Content-Type: application/json' \ -H 'X-API-Key: ilk_your_key_here' \ -d '{"url":"https://example.com/long"}' - A valid key raises your rate limit, marks you as the owner of links you create, and enables private links.
- Keys are stored only as SHA-256 hashes — the plaintext is shown once at creation, so save it.
Rate limits
Applied to POST /api/urls. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset; a 429 also includes Retry-After.
| Caller | Limit | Window |
|---|---|---|
| Anonymous (per IP) | 10 requests | 60 seconds |
| Authenticated (per key) | 100 requests | 60 seconds |
GET and POST /api/codes use a separate quota so generating barcodes does not eat your shortening budget:
| Caller | Limit | Window |
|---|---|---|
| Anonymous (per IP) | 30 requests | 60 seconds |
| Authenticated (per key) | 120 requests | 60 seconds |
Public redirects (/{code}) are also capped to limit scrapers and redirect abuse: 120 / IP, 60 / IP+code, and 300 / code per 60 seconds. Over-limit redirects return 429.
Link safety
When a short link is created, ilync checks the destination before it is saved. Failed checks return 400 with a clear error — nothing extra is required on the request.
Public http(s) only
Destinations need a real domain. Other schemes are rejected.
No embedded credentials
URLs like user:pass@host are blocked — a common phishing trick.
No internal networks
Private, local, and reserved destinations never get shortened.
Unsafe destinations refused
Known-dangerous or disallowed URLs return 400 with a clear message.
Operator denylist
Extra hostnames can be blocked site-wide without code changes.
Automatic on create
No extra request fields — every POST /api/urls is checked.
Phishing detection
Every destination is scored for phishing signals before the link is saved.
Screened for life
Saved links are re-checked over time — destinations that turn hostile later get quarantined or disabled automatically.
{ "error": "That destination is not allowed (private or reserved host)." }Safety doesn't stop at creation
Destinations are re-evaluated over a link's lifetime. If a site turns hostile after the link was saved, it stops redirecting silently: visitors see a warning interstitial describing the risk before choosing to continue, and severely dangerous destinations are disabled outright.
Score any URL without shortening it
The same pipeline backs a public endpoint — no account or key required. Send a URL to POST /api/safety-check (rate-limited to 10 requests per minute per IP) and receive a 0–100 score, a safe / suspicious / dangerous verdict, and every signal that ran:
curl -s https://ilync.dev/api/safety-check \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com"}'{
"url": "https://example.com/",
"host": "example.com",
"score": 96,
"rating": "safe",
"signals": [
{ "id": "dns", "status": "pass", "detail": "Resolved to a public address." },
{ "id": "ml-phishing", "status": "pass", "detail": "No phishing signals detected." }
]
}Private URLs & access control
- Owner signs in and mints a key → gets
apiKeyanduserId(say 1). - Owner creates a link with
"private": trueusing their key. - Following the link works only with the owner's key; anyone else gets 401.
- The other user signs in and mints their own key to obtain their
userId(say 2). - Owner grants access:
POST /api/urls/{code}/accesswith{ "userId": 2 }. - User 2 can now follow the link with their key.
Because access is enforced via an API-key header, private links are API-oriented — not a click-through-in-a-browser flow unless the client sends the key.
# 1. Owner creates a private link with their key
curl -X POST https://ilync.dev/api/urls \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ilk_owner_key' \
-d '{"url":"https://example.com/secret","private":true}'
# → { "shortCode": "Ab3xK9", "isPrivate": true, ... }
# 2. Owner grants user 2 access
curl -X POST https://ilync.dev/api/urls/Ab3xK9/access \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ilk_owner_key' \
-d '{"userId":2}'
# 3. User 2 follows the link with their own key (owner can too)
curl -i https://ilync.dev/Ab3xK9 \
-H 'Authorization: Bearer ilk_user2_key'
# → 307 redirect to https://example.com/secret
# 4. Anonymous / non-granted users are refused
curl -i https://ilync.dev/Ab3xK9 # → 401Passcode-protected links
A passcode is a shared secret that visitors must enter before a short link redirects. It is different from private links: anyone who knows the code can reach the passcode page, but only people with the passcode get redirected.
How it works
- Only signed-in users (or API-key holders) can protect a link. Anonymous callers cannot set a passcode.
- The passcode is never stored or returned in plaintext — only a salted hash is kept. The API reports a boolean
hasPasscodeinstead. - Passcode-protected links are hidden from the public feed and are rate-limited so passcodes can't be brute-forced.
Visitor flow
- Opening
/{code}shows a passcode prompt. - A correct passcode sets a signed, short-lived (30 min), HttpOnly cookie and redirects to the destination.
- Incorrect entries return an error and are rate-limited per IP.
Setting or clearing a passcode
Set one when creating a link via POST /api/urls with a passcode field (requires auth), or from the Edit panel in your account dashboard.
/api/urls/{code}/passcodeOwnerChange or remove the passcode on an existing link you own. A non-empty passcode sets a new one; { "passcode": "" } or { "passcode": null } clears it.
curl -X PATCH https://ilync.dev/api/urls/Ab3xK9/passcode \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <firebase-id-token>' \
-d '{"passcode":"season-pass"}'curl -X PATCH https://ilync.dev/api/urls/Ab3xK9/passcode \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <firebase-id-token>' \
-d '{"passcode":""}'Expiration
- Set
expiresAton creation as an ISO 8601 string or epoch milliseconds. It must be in the future.create an expiring linkcurl -X POST https://ilync.dev/api/urls \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ilk_your_key_here' \ -d '{"url":"https://example.com/promo","expiresAt":"2026-12-31T23:59:59Z"}' - Expired links return
410 Goneand are removed on access (passive cleanup).following an expired linkcurl -i https://ilync.dev/Ab3xK9 # → 410 Gone - Run
POST /api/cleanupon a schedule for active cleanup.trigger active cleanupcurl -X POST https://ilync.dev/api/cleanup \ -H 'Authorization: Bearer <CLEANUP_SECRET>'
Privacy & cookies
ilync uses cookies and similar technologies for analytics, preferences, and authentication. You can review the full breakdown in the privacy policy. First-time visitors are shown a consent banner that lets them accept or decline analytics cookies.
Errors
Errors use standard HTTP status codes and return a JSON body with a human-readable error message. Always check the status code before parsing a success response.
{ "error": "Invalid URL: must be a valid http(s) address." }| Status | Meaning |
|---|---|
400 | Malformed or blocked request — bad URL, unsafe destination, JSON, or expiry. |
401 | Missing or invalid credentials. |
403 | Authenticated, but not allowed (e.g. not the owner). |
404 | Unknown short code. |
410 | The link has expired. |
429 | Rate limit exceeded on create or redirect — retry after the window resets. |