Zum Hauptinhalt springen

REST-API

MSK Shortener bringt eine vollständige JSON-REST-API mit. Alles, was die Weboberfläche kann, geht auch programmatisch, praktisch für Kommandozeilenwerkzeuge, Skripte, CI-Pipelines und die Anbindung an Discord-Bots.

Basis-URL: https://s.msk-scripts.de/api, bei einer eigenen Instanz entsprechend deine Domain.


Authentifizierung

Es gibt keinen API-Key. Alle Endpunkte sind öffentlich. Schreibende Aufrufe (anlegen und löschen) sind abgesichert über:

  • eine Ratenbegrenzung von 20 Anlagen pro Stunde je IP-Hash
  • einmalige Lösch-Token, die beim Anlegen zurückkommen, für DELETE
  • eine eigene Begrenzung beim Prüfen, 10 Passwortversuche je 5 Minuten bei geschützten Links

Aufbau einer Fehlermeldung

Alle Fehler haben dieselbe Form:

{
"error": "Lesbare Meldung",
"details": {
"feldName": ["Validierungsfehler 1", "Validierungsfehler 2"]
}
}

details gibt es nur bei 400 Bad Request, also bei fehlgeschlagener Validierung.

StatusBedeutung
400Validierung fehlgeschlagen, siehe details
401Falsches Passwort oder fehlender Lösch-Token
404Link gibt es nicht oder der Token ist ungültig
409Der eigene Kurzcode ist bereits vergeben
429Ratenbegrenzung erreicht, siehe Header Retry-After

POST /api/links, einen Link anlegen

Anfrage

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"
}

Die Felder

FeldTypPflichtHinweise
urlstringjaNur http:// oder https://. Höchstens 2048 Zeichen. Private IPs werden abgelehnt.
customCodestringnein3 bis 20 Zeichen, [a-zA-Z0-9_-]. Ohne Angabe wird einer erzeugt.
passwordstringnein4 bis 100 Zeichen.
expiresAtstring (ISO 8601)neinMuss in der Zukunft liegen.

Antwort (201 Created)

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

Dazu kommen Header zur Ratenbegrenzung:

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

Der deleteToken ist der einzige Weg, den Link später zu löschen. Sichere ihn, er lässt sich nicht erneut abrufen.

Beispiel

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"
}'

Verhalten

  • Liefert die Metadaten des Links.
  • Hat der Link ein Passwort, steht die originalUrl nicht in der Antwort, dafür ist /api/verify da.
  • Ist der Link abgelaufen, bleibt die originalUrl ebenfalls zurückgehalten.

Antwort

{
"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"
}

Beispiel

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

POST /api/verify, einen geschützten Link freischalten

Über diesen Endpunkt holst du die Ziel-URL eines passwortgeschützten Links. Bei Erfolg erhöht er außerdem den Klickzähler und legt eine anonymisierte Klickzeile an.

Anfrage

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

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

Antwort

200 OK liefert die Ziel-URL:

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

Bei falschem Passwort kommt 401 Unauthorized. Die Meldung ist bewusst allgemein gehalten, damit nicht durchsickert, ob es den Link überhaupt gibt.

Nach 10 Fehlversuchen binnen 5 Minuten vom selben IP-Hash kommt 429 Too Many Requests.

Beispiel

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

Braucht den Lösch-Token als Bearer-Header. Es wird kaskadierend gelöscht, alle Klickzeilen des Links verschwinden mit.

Anfrage

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

Antwort

Bei Erfolg 200 OK:

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

Ist Link oder Token ungültig, kommt 404 Not Found. Die API unterscheidet das bewusst nicht, damit sich Tokens nicht durch Ausprobieren finden lassen.

Beispiel

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

Liefert die vollständige Statistik eines einzelnen Kurzlinks, dieselben Daten wie auf der öffentlichen Statistikseite.

Query-Parameter

ParameterStandardHinweise
days30Länge des Zeitverlaufs. Mindestens 1, höchstens 365.

Antwort

{
"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 }]
}

Beispiel

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

GET /api/links/:code/qr, QR-Code

Liefert einen QR-Code, der die Kurz-URL enthält.

Query-Parameter

ParameterStandardErlaubt
formatpngpng, svg

Antwort

  • PNG: image/png, 512 × 512, MSK-Farben (#1b1b1d auf Weiß)
  • SVG: image/svg+xml, vektorbasiert und skalierbar
  • Beide Antworten tragen Content-Disposition: inline; filename="msk-<code>.<ext>" und werden 24 Stunden zwischengespeichert.

Beispiel

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, globale Statistik

Liefert die anonymen Gesamtzahlen, die auch auf der Seite /stats stehen. Wird 5 Minuten zwischengespeichert.

Antwort

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

Ratenbegrenzung

Der Endpunkt zum Anlegen ist standardmäßig auf 20 Anfragen pro Stunde und IP-Hash begrenzt. Ist das erreicht:

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 steht in Sekunden. Bei einer eigenen Instanz änderst du die Grenze über RATE_LIMIT_CREATE_PER_HOUR.

Der Endpunkt zum Prüfen hat eine eigene Grenze von 10 Versuchen je 5 Minuten und IP-Hash, als Schutz gegen Durchprobieren.


Beispiel: Kürzen von der Kommandozeile

Eine knappe Bash-Funktion, die eine URL kürzt:

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'
}

# Aufruf:
mskshort "https://example.com/sehr/lange/url"

Beispiel: 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)

Beispiel: 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"])