SecretNotes – API

Eine Notiz pro Ableton-Set, pro Nutzer – und auf Wunsch mit anderen geteilt.

Was ist das? SecretNotes speichert zu jedem Ableton-Set genau eine Notiz. Der Name der Notiz ist der Name des Sets. Jeder Nutzer hat seine eigenen Notizen und kann einzelne davon anderen Nutzern zum Lesen oder Schreiben freigeben.

In 3 Schritten loslegen

1

Token holen

Weg A – per API: POST /api/login mit einem device-Namen. Der Server legt dafür einen API-Key an und gibt den Token zurück (kein Cookie).

curl -s https://notes.secret.vip/api/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"ich@example.com","password":"geheim123","device":"Ableton Live (MacBook)"}'

Antwort:

{
  "token": "smn_…",
  "user": {"id": 1, "email": "ich@example.com", "name": "Sebastian", "is_admin": false, "email_verified": true, "created_at": "2026-09-01T18:22:04.000Z"}
}

Weg B – auf der Website: Einloggen → Konto → API-Keys → neuen Key erzeugen. Der Token wird nur ein einziges Mal angezeigt – direkt kopieren.

2

Notiz lesen

Der Notizname steht im Pfad und muss URL-kodiert sein: Leerzeichen → %20. Aus Mein Track wird /api/notes/Mein%20Track.

curl -s https://notes.secret.vip/api/notes/Mein%20Track \
  -H 'Authorization: Bearer smn_…'

Antwort (Notiz existiert noch nicht):

{
  "name": "Mein Track",
  "text": "",
  "updated_at": null,
  "exists": false,
  "owner": {"email": "ich@example.com", "name": "Sebastian"},
  "mine": true,
  "permission": "owner"
}
3

Notiz schreiben

PUT ersetzt den ganzen Text und legt die Notiz an, falls sie fehlt.

curl -s -X PUT https://notes.secret.vip/api/notes/Mein%20Track \
  -H 'Authorization: Bearer smn_…' \
  -H 'Content-Type: application/json' \
  -d '{"text":"Kick lauter\nVocals ab 1:30"}'

Antwort:

{
  "name": "Mein Track",
  "updated_at": "2026-09-03T10:15:30.123Z",
  "owner": {"email": "ich@example.com", "name": "Sebastian"},
  "permission": "owner"
}

Fertig. Für Automationen, die nur Zeilen anhängen wollen, ist PATCH mit append einfacher – dort ist kein Konfliktcheck nötig.

Authentifizierung

Zwei Wege, gleichwertig für alle /api/*-Routen:

  1. API-Key (Bearer) – für Geräte, Skripte, n8n und fremde Programme:
    Header Authorization: Bearer smn_<hex>
  2. Session-Cookie – nur für die Website. Wird von POST /api/login ohne device gesetzt (HttpOnly, 30 Tage gültig). Cookies werden cross-origin nicht mitgesendet – für alles außerhalb der Website also immer Bearer verwenden.

Ohne gültige Anmeldung antwortet der Server mit 401 {"error":"unauthorized"}.

Öffentlich (ohne Anmeldung erreichbar): GET /api/health, POST /api/login, POST /api/register, GET /api/register/info, POST /api/register/verify, POST /api/register/resend, GET /api/captcha, POST /api/password/forgot, POST /api/password/reset, GET /api/docs, GET /api/openapi.json.

CORS: Access-Control-Allow-Origin: * auf /api/*. Bearer-Aufrufe funktionieren aus jedem Browser oder Programm; OPTIONS-Preflight wird beantwortet.

Token nie in URLs. Der Token gehört ausschließlich in den Authorization-Header – nie als Query-Parameter, nie in Links, nie in Logs oder Screenshots. Ein kompromittierter Key lässt sich unter API-Keys jederzeit widerrufen.

Konten

Das Objekt user sieht überall gleich aus. Die E-Mail wird kleingeschrieben gespeichert.

{"id": 1, "email": "ich@example.com", "name": "Sebastian", "is_admin": false, "email_verified": true, "created_at": "2026-09-01T18:22:04.000Z"}

POST/api/registeröffentlich

Legt ein neues Konto an. Auf notes.secret.vip gilt Double-Opt-in: Es kommt eine Bestätigungs-Mail von hello@secret.vip mit einem 24 Stunden gültigen Link – erst nach dem Klick ist die Anmeldung möglich. Erstes Konto und Einladungen sind sofort angemeldet.

Body

Erlaubt, wenn eine Bedingung zutrifft: noch kein Nutzer existiert (der erste wird Admin) oder invite gültig ist oder der Server offen ist (SMN_OPEN_REGISTRATION=1). Auf notes.secret.vip ist die Registrierung offen; pro IP sind 5 Registrierungen pro Stunde möglich.

Passwort: mindestens 8 Zeichen, kein Allerweltspasswort (Blockliste), nicht nur ein wiederholtes Zeichen, nicht gleich der E-Mail – sonst 400 weak_password.

Antwort 200

// Bestätigung ausstehend (Normalfall auf notes.secret.vip) – kein Cookie
{"user": {"id": 2, "email": "neu@example.com", "name": "Neu", "is_admin": false, "email_verified": false, "created_at": "2026-09-03T09:00:00.000Z"}, "verification_required": true, "verification_sent": true}

// erstes Konto, Einladung oder Server ohne E-Mail-Versand → sofort angemeldet, Set-Cookie
{"user": {"id": 2, "email": "neu@example.com", "name": "Neu", "is_admin": false, "email_verified": true, "created_at": "2026-09-03T09:00:00.000Z"}}

verification_sent: false heißt: Konto angelegt, aber die Mail konnte gerade nicht versendet werden – über POST /api/register/resend neu anfordern. Nicht bestätigte Konten werden nach 7 Tagen automatisch gelöscht.

Fehler

400 invalid_body 400 weak_password 400 invalid_invite 400 registration_closed 403 captcha_required (Captcha fehlt, falsch oder abgelaufen) 409 email_taken 429 too_many_attempts (mehr als 5 Registrierungen pro Stunde von dieser IP)

curl

curl -s https://notes.secret.vip/api/register \
  -H 'Content-Type: application/json' \
  -d '{"email":"neu@example.com","name":"Neu","password":"korrekt-pferd-batterie","captcha":{"id":"0f3a…","answer":"k7mpx"}}'

GET/api/register/infoöffentlich

Sagt, ob eine Registrierung gerade möglich ist – und prüft optional einen Einladungs-Token.

Query

Antwort 200

{"open": true, "first_user": false, "captcha_required": true, "password_reset": true, "email_verification": true, "min_password": 8}

// mit ?invite=<token>
{"open": true, "first_user": false, "captcha_required": true, "password_reset": true, "email_verification": true, "min_password": 8, "invite_valid": true, "invite_email": "neu@example.com"}

captcha_required: Registrierung braucht ein Captcha (nicht beim ersten Konto, nicht mit gültiger Einladung). password_reset: Passwort vergessen ist verfügbar (E-Mail-Versand eingerichtet). email_verification: neue Konten müssen die E-Mail-Adresse bestätigen (Bestätigungs-Mail); false nur, wenn kein E-Mail-Versand eingerichtet ist. min_password: Mindestlänge des Passworts.

invite_email ist null, wenn die Einladung nicht an eine bestimmte Adresse gebunden ist.

curl

curl -s 'https://notes.secret.vip/api/register/info?invite=a1b2c3…'

POST/api/register/verifyöffentlich

Bestätigt die E-Mail-Adresse mit dem Token aus der Bestätigungs-Mail und meldet das Konto per Session-Cookie an.

Body

Antwort 200 + Set-Cookie

{"ok": true, "user": {"id": 2, "email": "neu@example.com", "name": "Neu", "is_admin": false, "email_verified": true, "created_at": "2026-09-03T09:00:00.000Z"}}

Fehler

400 invalid_body 400 invalid_token (ungültig, abgelaufen oder schon benutzt – zählt als Fehlversuch) 429 too_many_attempts

curl

curl -s https://notes.secret.vip/api/register/verify \
  -H 'Content-Type: application/json' \
  -d '{"token":"ver_…"}'

POST/api/register/resendöffentlich

Schickt die Bestätigungs-Mail erneut (Absender hello@secret.vip).

Body

Die Antwort ist immer {"ok": true} – auch wenn es kein Konto gibt oder es schon bestätigt ist. Limits: 3 Mails pro E-Mail und Stunde, 10 Anfragen pro IP und Stunde.

Fehler

400 invalid_body 403 captcha_required 429 too_many_attempts 503 mail_unavailable

curl

curl -s https://notes.secret.vip/api/register/resend \
  -H 'Content-Type: application/json' \
  -d '{"email":"neu@example.com","captcha":{"id":"0f3a…","answer":"k7mpx"}}'

GET/api/captchaöffentlich

Holt einen Sicherheitscode als Bild (SVG). Wird auf dem Server erzeugt – kein Drittanbieter, kein Tracking.

Der Code ist einmal verwendbar und 10 Minuten gültig; Groß-/Kleinschreibung ist egal. Jede Prüfung verbraucht ihn – nach einer falschen Antwort also neu holen. Nötig bei Registrierung, Bestätigungs-Mail erneut senden, Passwort vergessen und beim Login, sobald der Server 403 captcha_required antwortet (nach 3 Fehlversuchen pro IP oder pro E-Mail).

Antwort 200

{"id": "0f3a…", "svg": "<svg xmlns=…>…</svg>", "ttl": 600}

Beim nächsten Aufruf mitschicken als "captcha": {"id": "0f3a…", "answer": "k7mpx"}.

Fehler

404 not_found (Captcha auf diesem Server abgeschaltet) 429 too_many_attempts (mehr als 30 Bilder in 10 Minuten pro IP)

curl

curl -s https://notes.secret.vip/api/captcha | jq -r .svg > captcha.svg   # Bild ansehen, Code abtippen

POST/api/loginöffentlich

Meldet an – entweder als Website-Session (Cookie) oder als Gerät mit eigenem API-Key (Token).

Body

Antwort 200

// ohne device → Session-Cookie wird gesetzt
{"user": {"id": 1, "email": "ich@example.com", "name": "Sebastian", "is_admin": false, "email_verified": true, "created_at": "2026-09-01T18:22:04.000Z"}}

// mit device → Token, kein Cookie
{"token": "smn_…", "user": {"id": 1, "email": "ich@example.com", "name": "Sebastian", "is_admin": false, "email_verified": true, "created_at": "2026-09-01T18:22:04.000Z"}}

Fehler

400 invalid_body 401 wrong_credentials 403 captcha_required (Captcha nötig, fehlt oder falsch – neues Bild holen) 403 email_unverified (E-Mail-Adresse noch nicht bestätigt – Link aus der Mail öffnen oder neu anfordern) 403 quota_exceeded (mit device: schon 50 Geräte-Zugänge) 429 too_many_attempts (10 Fehlversuche in 15 Minuten pro IP)

curl

curl -s https://notes.secret.vip/api/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"ich@example.com","password":"geheim123","device":"Mein Skript"}'

POST/api/password/forgotöffentlich

Schickt einen Link zum Zurücksetzen des Passworts an die Konto-E-Mail (Absender hello@secret.vip).

Body

Die Antwort ist immer {"ok": true} – auch wenn es kein Konto mit dieser E-Mail gibt (verrät nicht, welche Adressen registriert sind). Der Link führt auf https://notes.secret.vip/?reset=<token>, ist 1 Stunde gültig und einmal nutzbar. Limits: 3 Anfragen pro E-Mail und Stunde, 10 pro IP und Stunde.

Antwort 200

{"ok": true}

Fehler

400 invalid_body 403 captcha_required 429 too_many_attempts 503 mail_unavailable (kein E-Mail-Versand eingerichtet)

curl

curl -s https://notes.secret.vip/api/password/forgot \
  -H 'Content-Type: application/json' \
  -d '{"email":"ich@example.com","captcha":{"id":"0f3a…","answer":"k7mpx"}}'

POST/api/password/resetöffentlich

Setzt mit dem Token aus der E-Mail ein neues Passwort.

Body

Danach sind alle Sitzungen und alle Geräte-Zugänge (API-Keys) des Kontos widerrufen – Plugins müssen sich neu anmelden. Es wird keine Session gesetzt; anschließend normal anmelden. Ungültige Tokens zählen als Fehlversuch.

Antwort 200

{"ok": true}

Fehler

400 invalid_body 400 weak_password 400 invalid_token (unbekannt, abgelaufen oder schon benutzt) 429 too_many_attempts

curl

curl -s https://notes.secret.vip/api/password/reset \
  -H 'Content-Type: application/json' \
  -d '{"token":"rst_…","password":"korrekt-pferd-batterie"}'

POST/api/logout

Beendet die Session – oder widerruft den API-Key, mit dem der Aufruf gemacht wurde.

Antwort 200

{"ok": true}

Fehler

401 unauthorized

curl

curl -s -X POST https://notes.secret.vip/api/logout -H 'Authorization: Bearer smn_…'

GET/api/me

Gibt das eigene Konto zurück – praktisch, um einen Token zu prüfen.

Antwort 200

{"user": {"id": 1, "email": "ich@example.com", "name": "Sebastian", "is_admin": false, "email_verified": true, "created_at": "2026-09-01T18:22:04.000Z"}}

Fehler

401 unauthorized

curl

curl -s https://notes.secret.vip/api/me -H 'Authorization: Bearer smn_…'

PATCH/api/me

Ändert den eigenen Anzeigenamen und/oder das Passwort.

Body

Antwort 200

{"user": {"id": 1, "email": "ich@example.com", "name": "Sebastian B.", "is_admin": false, "email_verified": true, "created_at": "2026-09-01T18:22:04.000Z"}}

Fehler

400 invalid_body 400 weak_password 401 unauthorized 401 wrong_credentials (falsches current_password)

curl

curl -s -X PATCH https://notes.secret.vip/api/me \
  -H 'Authorization: Bearer smn_…' \
  -H 'Content-Type: application/json' \
  -d '{"password":"nochGeheimer456","current_password":"geheim123"}'

Notizen

Eine Notiz gehört genau einem Nutzer und hat pro Nutzer einen eindeutigen Namen – den Namen des Ableton-Sets.

Namensregel: 1–200 Zeichen (Unicode), kein /, kein \, keine Steuerzeichen, kein führender Punkt. Sonst 400 invalid_name.

URL-Encoding: Der Name steht im Pfad und wird URL-kodiert. Leerzeichen → %20, Umlaute z. B. ü → %C3%BC. In JavaScript encodeURIComponent(name), in Python urllib.parse.quote(name).

Mein Track        → /api/notes/Mein%20Track
Späti Session 2   → /api/notes/Sp%C3%A4ti%20Session%202

permission ist immer eines von "owner" (gehört mir), "write" (geteilt, darf schreiben), "read" (geteilt, nur lesen).

Welche Notiz ist gemeint? – der Query-Parameter owner

Weil mehrere Nutzer eine Notiz mit demselben Namen haben können (z. B. beide arbeiten an „Mein Track“), steuert ?owner=… bei GET, PUT und PATCH auf /api/notes/:name, welche Notiz angesprochen wird:

AufrufBedeutung
?owner=meNur meine eigene Notiz. Geteilte Notizen werden ignoriert.
?owner=freund@example.comDie Notiz dieses Nutzers, die mit mir geteilt ist. Gibt es keine solche Freigabe → 404 not_found.
ohne ownerAutomatische Auflösung: (1) meine eigene Notiz, falls vorhanden; (2) sonst eine mit mir geteilte Notiz dieses Namens – bei mehreren die zuletzt geänderte; (3) sonst „existiert nicht“ – die Notiz gehört dann mir und wird bei PUT/PATCH neu angelegt.
Empfehlung: Wer nur an eigenen Notizen arbeitet, nutzt ?owner=me – dann landet nie versehentlich etwas in einer fremden, geteilten Notiz. Die Ableton-Integration lässt owner weg, damit geteilte Sets automatisch gefunden werden.

Bei DELETE wird owner ignoriert – gelöscht werden kann immer nur die eigene Notiz.

GET/api/notes

Listet alle eigenen und alle mit mir geteilten Notizen – neueste zuerst.

Antwort 200

{
  "notes": [
    {
      "name": "Mein Track",
      "owner": {"email": "ich@example.com", "name": "Sebastian"},
      "mine": true,
      "permission": "owner",
      "updated_at": "2026-09-03T10:15:30.123Z",
      "size": 27
    },
    {
      "name": "Late Night Jam",
      "owner": {"email": "freund@example.com", "name": "Alex"},
      "mine": false,
      "permission": "write",
      "updated_at": "2026-09-02T22:41:09.000Z",
      "size": 412
    }
  ]
}

size = Größe des Textes in Bytes. mine = ob die Notiz mir gehört.

Fehler

401 unauthorized

curl

curl -s https://notes.secret.vip/api/notes -H 'Authorization: Bearer smn_…'

GET/api/notes/:name

Liest eine Notiz. Existiert sie nicht, kommt trotzdem 200 – mit exists:false und leerem Text.

Query

Header (optional)

Antwort 200

{
  "name": "Mein Track",
  "text": "Kick lauter\nVocals ab 1:30",
  "updated_at": "2026-09-03T10:15:30.123Z",
  "exists": true,
  "owner": {"email": "ich@example.com", "name": "Sebastian"},
  "mine": true,
  "permission": "owner"
}

// Notiz existiert nicht
{"name": "Neues Set", "text": "", "updated_at": null, "exists": false,
 "owner": {"email": "ich@example.com", "name": "Sebastian"}, "mine": true, "permission": "owner"}

ETag / If-None-Match / 304

Jede 200-Antwort trägt einen Header ETag: "<ms>" – der Änderungszeitpunkt in Millisekunden, in Anführungszeichen. Wer regelmäßig nachfragt (Polling), schickt diesen Wert beim nächsten Mal als If-None-Match mit. Hat sich nichts geändert, antwortet der Server mit 304 Not Modified und leerem Body – spart Traffic und Parsing.

# 1. Lesen – ETag merken
curl -si https://notes.secret.vip/api/notes/Mein%20Track -H 'Authorization: Bearer smn_…'
# HTTP/1.1 200 OK
# ETag: "1788430530123"
# …

# 2. Später nachfragen – nur laden, wenn sich etwas geändert hat
curl -si https://notes.secret.vip/api/notes/Mein%20Track \
  -H 'Authorization: Bearer smn_…' \
  -H 'If-None-Match: "1788430530123"'
# HTTP/1.1 304 Not Modified   (kein Body)

Fehler

400 invalid_name 401 unauthorized 404 not_found (nur bei ?owner=<email> ohne passende Freigabe)

curl

# eigene Notiz
curl -s 'https://notes.secret.vip/api/notes/Mein%20Track?owner=me' -H 'Authorization: Bearer smn_…'

# mit mir geteilte Notiz eines bestimmten Nutzers
curl -s 'https://notes.secret.vip/api/notes/Mein%20Track?owner=freund@example.com' -H 'Authorization: Bearer smn_…'

PUT/api/notes/:name

Ersetzt den gesamten Text einer Notiz; legt sie an, falls sie fehlt. Optional mit Konfliktschutz.

Query

Body (Content-Type muss application/json sein)

Antwort 200

{
  "name": "Mein Track",
  "updated_at": "2026-09-03T10:15:30.123Z",
  "owner": {"email": "ich@example.com", "name": "Sebastian"},
  "permission": "owner"
}

Konflikt-Verfahren (base_updated_at → 409)

Ohne base_updated_at gewinnt immer der letzte Schreiber – gut für Skripte, riskant für Editoren. Mit base_updated_at läuft es so:

  1. Client liest die Notiz und merkt sich updated_at (z. B. "2026-09-03T10:15:30.123Z"; bei exists:false ist es null).
  2. Client schickt beim PUT genau diesen Wert als base_updated_at mit.
  3. Stimmt er mit dem Stand auf dem Server überein → 200, gespeichert.
  4. Weicht er ab (jemand anderes hat zwischenzeitlich gespeichert) → 409 {"error":"conflict", …}. Der Body enthält den aktuellen Serverstand (text, updated_at). Nichts wurde überschrieben.
  5. Client zeigt beide Fassungen, lässt zusammenführen und schickt erneut – jetzt mit dem updated_at aus der 409-Antwort als base_updated_at.

Sonderfall: base_updated_at ist gesetzt, aber die Notiz existiert auf dem Server nicht (mehr) → ebenfalls 409, mit text:"" und updated_at:null.

# Client hat um 10:15:30.123 gelesen, jemand anderes hat um 10:20:00 gespeichert
curl -s -X PUT https://notes.secret.vip/api/notes/Mein%20Track \
  -H 'Authorization: Bearer smn_…' \
  -H 'Content-Type: application/json' \
  -d '{"text":"Kick lauter\nVocals ab 1:30\nHiHat leiser","base_updated_at":"2026-09-03T10:15:30.123Z"}'

# → HTTP 409
{
  "error": "conflict",
  "name": "Mein Track",
  "text": "Kick lauter\nVocals ab 1:30\nBass ab 0:45 doppeln",
  "updated_at": "2026-09-03T10:20:00.000Z",
  "owner": {"email": "ich@example.com", "name": "Sebastian"},
  "permission": "owner"
}

# Zusammenführen und erneut senden – mit dem neuen Stand als Basis
curl -s -X PUT https://notes.secret.vip/api/notes/Mein%20Track \
  -H 'Authorization: Bearer smn_…' \
  -H 'Content-Type: application/json' \
  -d '{"text":"Kick lauter\nVocals ab 1:30\nBass ab 0:45 doppeln\nHiHat leiser","base_updated_at":"2026-09-03T10:20:00.000Z"}'
# → HTTP 200

Fehler

400 invalid_name 400 invalid_json 400 invalid_body 401 unauthorized 403 forbidden (Freigabe ist nur-lesen) 403 quota_exceeded (Anlegen: schon 1000 Notizen) 404 not_found (?owner=<email> ohne Freigabe) 409 conflict 413 payload_too_large (> 1 MB) 415 unsupported_media_type (Content-Type fehlt/falsch)

curl

curl -s -X PUT 'https://notes.secret.vip/api/notes/Mein%20Track?owner=me' \
  -H 'Authorization: Bearer smn_…' \
  -H 'Content-Type: application/json' \
  -d '{"text":"Kick lauter\nVocals ab 1:30"}'

PATCH/api/notes/:name

Hängt Text als eigene Zeile an die Notiz an; legt sie an, falls sie fehlt. Gemacht für Automationen – kein Konfliktcheck nötig.

Query

Body (Content-Type application/json)

Antwort 200 – mit dem vollständigen neuen Text

{
  "name": "Mein Track",
  "updated_at": "2026-09-03T10:25:12.480Z",
  "text": "Kick lauter\nVocals ab 1:30\nMaster -1 dB",
  "owner": {"email": "ich@example.com", "name": "Sebastian"},
  "permission": "owner"
}

Fehler

400 invalid_name 400 invalid_json 400 invalid_body 401 unauthorized 403 forbidden (nur-lesen) 404 not_found 413 payload_too_large 415 unsupported_media_type

curl

curl -s -X PATCH https://notes.secret.vip/api/notes/Mein%20Track \
  -H 'Authorization: Bearer smn_…' \
  -H 'Content-Type: application/json' \
  -d '{"append":"Master -1 dB"}'

DELETE/api/notes/:name

Löscht die eigene Notiz samt aller Freigaben. Antwortet auch dann mit ok, wenn sie gar nicht existiert.

Nur der Besitzer darf löschen. ?owner wird ignoriert. Eine mit mir geteilte (fremde) Notiz kann ich nicht löschen → 403 forbidden.

Antwort 200

{"ok": true}

Fehler

400 invalid_name 401 unauthorized 403 forbidden

curl

curl -s -X DELETE https://notes.secret.vip/api/notes/Mein%20Track -H 'Authorization: Bearer smn_…'

Teilen nur Besitzer

Freigaben verwaltet ausschließlich der Besitzer einer Notiz. permission ist "read" (nur lesen) oder "write" (lesen + schreiben). Wer keine Freigabe hat, darf die Notiz nicht sehen.

GET/api/notes/:name/shares

Listet, mit wem die eigene Notiz geteilt ist.

Antwort 200

{
  "shares": [
    {"email": "freund@example.com", "name": "Alex", "permission": "write", "created_at": "2026-09-02T20:00:00.000Z"},
    {"email": "mix@example.com", "name": "Mia", "permission": "read", "created_at": "2026-09-03T08:12:45.000Z"}
  ]
}

Fehler

400 invalid_name 401 unauthorized 403 forbidden 404 not_found

curl

curl -s https://notes.secret.vip/api/notes/Mein%20Track/shares -H 'Authorization: Bearer smn_…'

PUT/api/notes/:name/shares/:email

Teilt die Notiz mit einem Nutzer oder ändert dessen Recht.

Body

Antwort 200

{"email": "freund@example.com", "name": "Alex", "permission": "write"}

Fehler

400 invalid_name 400 invalid_body 400 cannot_share_self 401 unauthorized 403 forbidden 403 quota_exceeded (schon 50 Freigaben an dieser Notiz) 404 not_found (Notiz fehlt) 404 user_not_found (kein Konto mit dieser E-Mail)

curl

curl -s -X PUT https://notes.secret.vip/api/notes/Mein%20Track/shares/freund@example.com \
  -H 'Authorization: Bearer smn_…' \
  -H 'Content-Type: application/json' \
  -d '{"permission":"write"}'

DELETE/api/notes/:name/shares/:email

Entzieht einem Nutzer die Freigabe.

Antwort 200

{"ok": true}

Fehler

400 invalid_name 401 unauthorized 403 forbidden 404 not_found

curl

curl -s -X DELETE https://notes.secret.vip/api/notes/Mein%20Track/shares/freund@example.com \
  -H 'Authorization: Bearer smn_…'

API-Keys eigene

Jeder Key ist ein Bearer-Token smn_…. Ein Key pro Gerät oder Automation ist sinnvoll – dann lässt sich jeder einzeln widerrufen.

GET/api/keys

Listet die eigenen API-Keys (ohne den geheimen Token – nur der Anfang als prefix).

Antwort 200

{
  "keys": [
    {"id": 3, "name": "Ableton Live (MacBook)", "prefix": "smn_9f2c", "created_at": "2026-09-01T18:30:00.000Z", "last_used_at": "2026-09-03T10:25:12.000Z", "current": true},
    {"id": 4, "name": "n8n", "prefix": "smn_0b7e", "created_at": "2026-09-02T09:00:00.000Z", "last_used_at": null, "current": false}
  ]
}

current = der Key, mit dem dieser Aufruf gemacht wurde. last_used_at ist null, wenn der Key noch nie benutzt wurde.

Fehler

401 unauthorized

curl

curl -s https://notes.secret.vip/api/keys -H 'Authorization: Bearer smn_…'

POST/api/keys

Erzeugt einen neuen API-Key. Der Token wird nur dieses eine Mal gezeigt.

Body

Antwort 200

{"id": 5, "name": "n8n Studio", "token": "smn_…"}

Fehler

400 invalid_body 401 unauthorized 403 quota_exceeded (schon 50 Geräte-Zugänge)

curl

curl -s https://notes.secret.vip/api/keys \
  -H 'Authorization: Bearer smn_…' \
  -H 'Content-Type: application/json' \
  -d '{"name":"n8n Studio"}'

DELETE/api/keys/:id

Widerruft einen API-Key sofort.

Antwort 200

{"ok": true}

Fehler

401 unauthorized 404 not_found

curl

curl -s -X DELETE https://notes.secret.vip/api/keys/5 -H 'Authorization: Bearer smn_…'

Admin nur is_admin

Alle Admin-Routen erfordern ein Konto mit is_admin:true – sonst 403 forbidden. Der erste registrierte Nutzer ist automatisch Admin.

GET/api/admin/users

Listet alle Nutzer mit Anzahl ihrer Notizen.

Antwort 200

{
  "users": [
    {"id": 1, "email": "ich@example.com", "name": "Sebastian", "is_admin": true, "email_verified": true, "created_at": "2026-09-01T18:22:04.000Z", "notes": 12},
    {"id": 2, "email": "freund@example.com", "name": "Alex", "is_admin": false, "email_verified": true, "created_at": "2026-09-02T19:40:00.000Z", "notes": 3}
  ]
}

Fehler

401 unauthorized 403 forbidden

curl

curl -s https://notes.secret.vip/api/admin/users -H 'Authorization: Bearer smn_…'

POST/api/admin/invites

Erzeugt einen Einladungslink – 7 Tage gültig, einmal nutzbar.

Body

Antwort 200

{
  "id": 7,
  "url": "https://notes.secret.vip/?invite=a1b2c3…",
  "email": "neu@example.com",
  "expires_at": "2026-09-10T10:30:00.000Z"
}

Fehler

400 invalid_body 401 unauthorized 403 forbidden

curl

curl -s https://notes.secret.vip/api/admin/invites \
  -H 'Authorization: Bearer smn_…' \
  -H 'Content-Type: application/json' \
  -d '{"email":"neu@example.com"}'

GET/api/admin/invites

Listet alle Einladungen – offene, abgelaufene und benutzte.

Antwort 200

{
  "invites": [
    {"id": 7, "email": "neu@example.com", "created_at": "2026-09-03T10:30:00.000Z", "expires_at": "2026-09-10T10:30:00.000Z", "used_at": null},
    {"id": 6, "email": null, "created_at": "2026-08-20T12:00:00.000Z", "expires_at": "2026-08-27T12:00:00.000Z", "used_at": "2026-08-21T09:15:00.000Z"}
  ]
}

Fehler

401 unauthorized 403 forbidden

curl

curl -s https://notes.secret.vip/api/admin/invites -H 'Authorization: Bearer smn_…'

DELETE/api/admin/invites/:id

Löscht eine Einladung; der Link funktioniert danach nicht mehr.

Antwort 200

{"ok": true}

Fehler

401 unauthorized 403 forbidden 404 not_found

curl

curl -s -X DELETE https://notes.secret.vip/api/admin/invites/7 -H 'Authorization: Bearer smn_…'

POST/api/admin/users/:id/password

Setzt einem Nutzer ein neues Passwort. Alle seine Sessions enden.

Body

Antwort 200

{"ok": true}

Fehler

400 invalid_body 400 weak_password 401 unauthorized 403 forbidden 404 user_not_found

curl

curl -s https://notes.secret.vip/api/admin/users/2/password \
  -H 'Authorization: Bearer smn_…' \
  -H 'Content-Type: application/json' \
  -d '{"password":"neuesPasswort789"}'

PATCH/api/admin/users/:id

Ändert Name und/oder Admin-Status eines Nutzers.

Body

Antwort 200

{"user": {"id": 2, "email": "freund@example.com", "name": "Alex", "is_admin": true, "email_verified": true, "created_at": "2026-09-02T19:40:00.000Z"}}

Fehler

400 invalid_body 401 unauthorized 403 forbidden 404 user_not_found

curl

curl -s -X PATCH https://notes.secret.vip/api/admin/users/2 \
  -H 'Authorization: Bearer smn_…' \
  -H 'Content-Type: application/json' \
  -d '{"is_admin":true}'

DELETE/api/admin/users/:id

Löscht einen Nutzer komplett – samt Notizen, API-Keys und Freigaben. Nicht rückgängig zu machen.

Antwort 200

{"ok": true}

Fehler

400 cannot_delete_self (eigenes Konto) 401 unauthorized 403 forbidden 404 user_not_found

curl

curl -s -X DELETE https://notes.secret.vip/api/admin/users/2 -H 'Authorization: Bearer smn_…'

Sonstiges

GET/api/healthöffentlich

Lebenszeichen des Servers – für Monitoring.

Antwort 200

{"ok": true}

curl

curl -s https://notes.secret.vip/api/health

GET/api/docsöffentlich

Diese Dokumentation als HTML.

curl

curl -s https://notes.secret.vip/api/docs

GET/api/openapi.jsonöffentlich

Maschinenlesbare Beschreibung der API (OpenAPI 3.0) – z. B. für Postman, Insomnia oder Code-Generatoren.

curl

curl -s https://notes.secret.vip/api/openapi.json

n8n / Automationen

Für Automationen ist der Bearer-Token der richtige Weg. Am besten pro Automation einen eigenen API-Key anlegen.

n8n: HTTP-Request-Node einrichten

  1. Node HTTP Request hinzufügen.
  2. Authentication → Generic Credential Type → Header Auth.
  3. Neue Credential anlegen: Name = Authorization, Value = Bearer smn_… (dein Token).
  4. Bei PUT/PATCH/POST: Send Body aktivieren, Body Content Type JSON. n8n setzt dann Content-Type: application/json. Falls du den Header manuell setzt: Send Headers → Content-Type = application/json.
  5. URL mit kodiertem Notiznamen, z. B. https://notes.secret.vip/api/notes/Mein%20Track. Bei dynamischen Namen: {{ encodeURIComponent($json.setName) }}.

Typische Anwendungsfälle

ZielNode-EinstellungBody (JSON)
Zeile an eine Notiz anhängen
(z. B. „Export fertig 14:32“, Kalender-Eintrag, Reminder)
PATCH /api/notes/Mein%20Track {"append":"Export fertig 14:32"}
Alle Notizen auflisten
(z. B. Tages-Digest, Backup, Suche)
GET /api/notes –

PATCH ist für Automationen ideal: keine Konflikte, kein Vorher-Lesen, legt die Notiz bei Bedarf an – und gibt den vollständigen neuen text zurück.

JavaScript (fetch)

const token = "smn_…";
const name = "Mein Track";
const res = await fetch("https://notes.secret.vip/api/notes/" + encodeURIComponent(name), {
  method: "PATCH",
  headers: { "Authorization": "Bearer " + token, "Content-Type": "application/json" },
  body: JSON.stringify({ append: "Master -1 dB" })
});
if (!res.ok) throw new Error("SMN " + res.status + ": " + (await res.json()).error);
console.log((await res.json()).text);

Python (requests)

import requests
from urllib.parse import quote

TOKEN = "smn_…"
headers = {"Authorization": f"Bearer {TOKEN}"}
url = "https://notes.secret.vip/api/notes/" + quote("Mein Track")
r = requests.patch(url, headers=headers, json={"append": "Master -1 dB"})
r.raise_for_status()          # wirft bei 4xx/5xx; Details in r.json()["error"]
print(r.json()["text"])

json= setzt in requests automatisch Content-Type: application/json.

Fehlercodes

Fehler kommen immer als {"error":"<code>"}. Ausnahme: 409 conflict enthält zusätzlich den Serverstand der Notiz (siehe PUT).

HTTPerrorBedeutung
400invalid_nameNotizname verletzt die Namensregel (Länge, /, \, Steuerzeichen, führender Punkt).
400invalid_bodyPflichtfeld fehlt oder hat den falschen Typ.
400invalid_jsonBody ist kein gültiges JSON.
400weak_passwordPasswort zu schwach: unter 8 Zeichen, Allerweltspasswort, nur ein wiederholtes Zeichen oder gleich der E-Mail.
400cannot_share_selfEine Notiz kann nicht mit sich selbst geteilt werden.
400cannot_delete_selfAdmin kann das eigene Konto nicht löschen.
400registration_closedRegistrierung ist geschlossen – Einladung nötig.
400invalid_inviteEinladungs-Token unbekannt, abgelaufen oder schon benutzt.
400invalid_tokenPasswort-Reset-Token unbekannt, abgelaufen oder schon benutzt.
401unauthorizedKein oder ungültiger Token bzw. keine gültige Session.
401wrong_credentialsE-Mail/Passwort falsch – oder current_password stimmt nicht.
403forbiddenKeine Berechtigung: nur-lesen-Freigabe, fremde Notiz, kein Admin.
403captcha_requiredSicherheitscode nötig, fehlt, falsch oder abgelaufen – neues Bild über GET /api/captcha holen.
403email_unverifiedLogin: Passwort stimmt, aber die E-Mail-Adresse ist noch nicht bestätigt – Link aus der Bestätigungs-Mail öffnen oder POST /api/register/resend.
403quota_exceededKontingent erreicht (1000 Notizen pro Konto, 50 Freigaben pro Notiz, 50 Geräte-Zugänge). Body enthält zusätzlich limit und what.
404not_foundNotiz, Freigabe, Key oder Einladung nicht gefunden.
404user_not_foundKein Nutzer mit dieser E-Mail bzw. ID.
409conflictNotiz wurde zwischenzeitlich geändert – Body enthält den Serverstand.
409email_takenE-Mail ist bereits registriert.
413payload_too_largeNotiz größer als 1 MB.
415unsupported_media_typeContent-Type ist nicht application/json.
429too_many_attemptsRate-Limit: 10 Login-Fehlversuche/15 min/IP, 5 Registrierungen/h/IP, 3 Passwort-Anfragen/h/E-Mail (10/h/IP), 30 Captchas/10 min/IP – kurz warten.
500server_errorUnerwarteter Serverfehler.
503mail_unavailableE-Mail-Versand auf diesem Server nicht eingerichtet – Passwort vergessen und Bestätigungs-Mail erneut senden nicht verfügbar.

Limits