Skip to main content

REST API

MSK Shortener exposes a full JSON REST API. Every feature in the web UI is available programmatically — useful for CLI tools, scripts, CI pipelines, and Discord-bot integrations.

Base URL: https://s.msk-scripts.de/api (or your self-hosted domain).


Authentication

There is no API key. All endpoints are public. Write operations (create / delete) are protected by:

  • Rate limiting (20 creates per hour per IP hash)
  • One-time delete tokens returned at creation time (for DELETE)
  • Verify rate limiting (10 password attempts per 5 minutes for protected links)

Error envelope

All errors share the same shape:

{
"error": "Human-readable message",
"details": {
"fieldName": ["validation error 1", "validation error 2"]
}
}

details is only present on 400 Bad Request (validation failures).

StatusMeaning
400Validation failed — see details
401Wrong password / missing delete token
404Link does not exist or token is invalid
409Custom short code is already in use
429Rate limit exceeded — see Retry-After header

POST /api/links — create a link

Request

POST /api/links HTTP/1.1
Content-Type: application/json

{
"url": "https://msk-scripts.de",
"customCode": "msk",
"password": "optional",
"expiresAt": "2026-12-31T23:59:59Z"
}

Field reference

FieldTypeRequiredNotes
urlstringyeshttp:// or https:// only. Max 2048 chars. Private IPs are rejected.
customCodestringno3–20 chars, [a-zA-Z0-9_-]. Auto-generated if omitted.
passwordstringno4–100 characters.
expiresAtstring (ISO 8601)noMust be in the future.

Response (201 Created)

{
"shortCode": "msk",
"shortUrl": "https://s.msk-scripts.de/msk",
"deleteToken": "dk_a7c4f2e1b9d8...",
"expiresAt": "2026-12-31T23:59:59.000Z",
"hasPassword": false
}

The response also includes rate-limit headers:

X-RateLimit-Limit: 20
X-RateLimit-Remaining: 19
X-RateLimit-Reset: 1764547200
warning

The deleteToken is the only way to delete the link later. Save it. It is not retrievable.

Example

curl -X POST https://s.msk-scripts.de/api/links \
-H "Content-Type: application/json" \
-d '{
"url": "https://msk-scripts.de",
"customCode": "msk",
"expiresAt": "2026-12-31T23:59:59Z"
}'

Behaviour

  • Returns metadata for the link.
  • If the link has a password, the originalUrl is not included in the response (use /api/verify instead).
  • If the link has expired, the originalUrl is also withheld.

Response

{
"shortCode": "msk",
"shortUrl": "https://s.msk-scripts.de/msk",
"originalUrl": "https://msk-scripts.de",
"hasPassword": false,
"expiresAt": "2026-12-31T23:59:59.000Z",
"clickCount": 42,
"createdAt": "2026-05-10T14:00:00.000Z"
}

Example

curl https://s.msk-scripts.de/api/links/msk

POST /api/verify — unlock a password-protected link

Use this endpoint to retrieve the destination URL of a password-protected link. On success, it also increments the click counter and stores an anonymized click row.

Request

POST /api/verify HTTP/1.1
Content-Type: application/json

{
"shortCode": "msk",
"password": "secret"
}

Response

200 OK returns the destination URL:

{ "originalUrl": "https://msk-scripts.de" }

401 Unauthorized is returned on wrong password. The message is intentionally generic to avoid leaking whether the link exists.

429 Too Many Requests after 10 failed attempts within 5 minutes from the same IP hash.

Example

curl -X POST https://s.msk-scripts.de/api/verify \
-H "Content-Type: application/json" \
-d '{"shortCode":"msk","password":"hunter2"}'

Requires the delete token as a Bearer header. Cascading delete: all click rows for the link are also removed.

Request

DELETE /api/links/msk HTTP/1.1
Authorization: Bearer dk_a7c4f2e1b9d8...

Response

200 OK on success:

{ "message": "Link erfolgreich gelöscht" }

404 Not Found if the link or token is invalid (the API does not differentiate, to avoid token probing).

Example

curl -X DELETE https://s.msk-scripts.de/api/links/msk \
-H "Authorization: Bearer dk_a7c4f2e1b9d8..."

Returns full statistics for a single short link — the same data shown on the public stats page.

Query parameters

ParameterDefaultNotes
days30Timeline length. Min 1, max 365.

Response

{
"shortCode": "msk",
"totalClicks": 42,
"createdAt": "2026-05-10T14:00:00.000Z",
"expiresAt": "2026-12-31T23:59:59.000Z",
"timeline": [
{ "date": "2026-05-10", "clicks": 5 },
{ "date": "2026-05-11", "clicks": 12 }
],
"browsers": [{ "name": "Chrome", "count": 28 }],
"operatingSystems": [{ "name": "Linux", "count": 19 }],
"devices": [{ "name": "desktop", "count": 35 }],
"topReferrers": [{ "host": "github.com", "count": 8 }]
}

Example

curl 'https://s.msk-scripts.de/api/links/msk/stats?days=7'

GET /api/links/:code/qr — QR code

Returns a QR code that encodes the short URL.

Query parameters

ParameterDefaultAllowed
formatpngpng, svg

Response

  • PNG: image/png, 512×512, MSK colors (#1b1b1d on white)
  • SVG: image/svg+xml, vector, scalable
  • Both responses include Content-Disposition: inline; filename="msk-<code>.<ext>" and are cached for 24 hours.

Example

curl -o msk.png 'https://s.msk-scripts.de/api/links/msk/qr'
curl -o msk.svg 'https://s.msk-scripts.de/api/links/msk/qr?format=svg'

GET /api/stats — global statistics

Returns anonymous aggregate numbers shown on the /stats page. Cached for 5 minutes.

Response

{
"totalLinks": 1234,
"totalClicks": 98765,
"linksToday": 42,
"linksThisWeek": 187,
"topBrowsers": [{ "name": "Chrome", "count": 50321 }],
"topOperatingSystems": [{ "name": "Linux", "count": 22110 }],
"topDevices": [{ "name": "desktop", "count": 70000 }]
}

Rate limiting

The create endpoint is limited to 20 requests per hour per IP hash by default. When exceeded:

HTTP/1.1 429 Too Many Requests
Retry-After: 1742
X-RateLimit-Limit: 20
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1764547200
Content-Type: application/json

{ "error": "Zu viele Anfragen. Bitte später erneut versuchen." }

Retry-After is in seconds. Self-hosted instances can change the limit via RATE_LIMIT_CREATE_PER_HOUR.

The verify endpoint has a separate 10 attempts per 5 minutes limit per IP hash for brute-force protection.


Example: command-line shortener

A minimal Bash function to shorten a URL:

mskshort() {
curl -sS -X POST https://s.msk-scripts.de/api/links \
-H "Content-Type: application/json" \
-d "$(jq -n --arg u "$1" '{url: $u}')" \
| jq -r '.shortUrl'
}

# Usage:
mskshort "https://example.com/very/long/url"

Example: Node.js

const res = await fetch('https://s.msk-scripts.de/api/links', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: 'https://msk-scripts.de',
customCode: 'msk',
}),
})
const link = await res.json()
console.log(link.shortUrl)

Example: Python

import requests

r = requests.post(
"https://s.msk-scripts.de/api/links",
json={
"url": "https://msk-scripts.de",
"customCode": "msk",
},
)
print(r.json()["shortUrl"])