Zum Inhalt springen
    Early AccessAllgemeine Verfügbarkeit ab 2027 geplantZugang anfragen

    Entwickler-Dokumentation · Oktober 2026

    LabNote.Light API

    REST-Schnittstellen, Geräteanbindung und Webhooks: Endpunkte, Berechtigungen und Beispiele für die technische Integration. MCP und OAuth sind getrennt beschrieben.

    REST base URL

    https://vilasdqkwlszlulteqrb.supabase.co/functions/v1/api-v1
    Referenzen herunterladen

    Verbindung und Authentifizierung

    REST akzeptiert Organisations-API-Keys und persönliche Access Tokens (PAT) als Bearer-Token, nicht beliebige Anmeldetokens. API-Keys sind an eine Organisation gebunden. Ungebundene PATs benötigen X-Org-Id oder ?org_id=<uuid>; bei gebundenen Tokens darf die angegebene Organisation nicht abweichen. PAT-Mitgliedschaften werden bei jeder Anfrage geprüft. Tokens nur serverseitig speichern und nicht in Browsercode oder öffentliche Repositories übernehmen.

    read erlaubt reguläre Lesezugriffe; write schließt read ein. Der Audit-Endpunkt benötigt audit:read. Scopes ersetzen keine Rollen-, Abteilungs- oder Datensatzberechtigungen. Löschungen generischer Datensätze erfordern eine Administratorrolle. Geräte-, Messdaten- und Auftragsendpunkte setzen das zugehörige aktivierte Modul voraus. Signaturen werden nicht über generische REST-Schreibzugriffe gesetzt.

    bash
    export LABNOTE_API="https://vilasdqkwlszlulteqrb.supabase.co/functions/v1/api-v1"
    # LABNOTE_API_KEY: set securely in your environment
    curl -sS "$LABNOTE_API/v1/ping" \
      -H "Authorization: Bearer $LABNOTE_API_KEY"
    
    curl -sS "$LABNOTE_API/v1/experiments?limit=20" \
      -H "Authorization: Bearer $LABNOTE_PAT" \
      -H "X-Org-Id: <org-uuid>"

    REST-Endpunkte

    Die Pfade werden an die REST-Basis-URL angehängt. Methoden gelten für den jeweils genannten Listen- oder Einzelpfad; zulässige Felder und zusätzliche Berechtigungen unterscheiden sich je Ressource. Die Referenz beschreibt den vorhandenen Code, keine pauschale Freigabe für jede Organisation.

    GET /v1/health

    Dienststatus ohne Token; keine Prüfung der Integrationsberechtigungen.

    GET /v1/ping

    Tokenidentität, Organisation und Ressourcenliste.

    GET POST /v1/experiments GET PATCH PUT DELETE /v1/experiments/{id}

    Generisches CRUD; erlaubte Felder und datensatzabhängige Rechte.

    GET POST /v1/projects GET PATCH PUT DELETE /v1/projects/{id}

    Generisches CRUD; erlaubte Felder und datensatzabhängige Rechte.

    GET POST /v1/samples GET PATCH PUT DELETE /v1/samples/{id}

    Generisches CRUD; erlaubte Felder und datensatzabhängige Rechte.

    GET POST /v1/equipment GET PATCH PUT DELETE /v1/equipment/{id}

    Generisches CRUD; erlaubte Felder und datensatzabhängige Rechte. Benötigt lab_equipment.

    GET POST /v1/reagents GET PATCH PUT DELETE /v1/reagents/{id}

    Generisches CRUD; erlaubte Felder und datensatzabhängige Rechte.

    GET POST /v1/stock-items GET PATCH PUT DELETE /v1/stock-items/{id}

    Generisches CRUD; erlaubte Felder und datensatzabhängige Rechte.

    GET POST /v1/inventories GET PATCH PUT DELETE /v1/inventories/{id}

    Generisches CRUD; erlaubte Felder und datensatzabhängige Rechte.

    GET POST /v1/stock-movements GET DELETE /v1/stock-movements/{id}

    Bewegungen lesen und anlegen; keine aktualisierbaren Felder.

    GET POST /v1/equipment/{id}/{sub} PATCH PUT /v1/equipment/{id}/{sub}/{subId}

    sub: usage, maintenance, calibrations oder qualifications. Datensätze des zugänglichen Geräts.

    GET POST PUT PATCH DELETE /v1/equipment/{id}/remote

    Remote-Link; POST/PUT Upsert, PATCH vorhandener Link, DELETE 204. Kein Remote-Steuerungsprotokoll.

    GET POST /v1/devices DELETE /v1/devices/{id}

    Messgeräte registrieren und auflisten; Löschen nur als Administrator. Kein GET-Einzelpfad.

    GET POST /v1/results GET /v1/results/{id} GET /v1/results/{id}/chart POST /v1/results/upload

    Messdaten-Ingest, Rohdatei-Upload und Ergebnisdaten; analytics_qc-Modul erforderlich.

    GET POST /v1/connectors GET DELETE /v1/connectors/{id}

    Connector-Heartbeat und Geräteabgleich; Löschen nur als Administrator.

    GET /v1/audit

    Audit-Trail mit audit:read und zweiteiligem Cursor.

    GET POST /v1/jobs GET /v1/jobs/{id}

    Zugelassene Hintergrundjobs einreichen und Status lesen.

    GET POST /v1/notifications

    Nutzerbezogen lesen; mit write an Organisationsmitglieder senden.

    GET /v1/environmental-points GET POST /v1/environmental-readings

    Umgebungsüberwachung: Punkte auflisten und Messwerte (auch Batch) einspeisen; analytics_qc-Modul erforderlich.

    GET POST /v1/lab-orders GET /v1/lab-orders/{id} GET /v1/lab-results

    Aufträge mit Proben und freigegebene Ergebnisse; sample_intake-Modul erforderlich.

    Geräte und Messergebnisse

    Die REST-API nimmt Connector-Status, Geräte und Messergebnisse entgegen. Der lokal laufende OPC-UA-/LADS-Agent ist eine separate Integration; diese API stellt keinen universellen Gerätetreiber bereit. Ergebnisse gehen an /v1/results, Rohdateien an /v1/results/upload (multipart/form-data, file und device_id). Größenlimits: 25 MiB, für mzML 50 MiB. API-Keys können an Connector und Geräte gebunden sein; PATs nicht. Automatische Geräteregistrierung beim Ergebnis-Ingest erfordert devices:register oder Administrator-Scope. Gerätespezifische Zugriffe und Ergebniszuordnung müssen vor Einsatz geprüft werden.

    Ausgehende Webhooks

    Konfigurierte Webhook-Endpunkte abonnieren exakte Ereignisnamen oder Wildcards. Zustellungen erfolgen asynchron an zulässige HTTP(S)-Adressen; interne Netzwerkadressen werden blockiert. Bei Fehlern sind insgesamt bis zu fünf Versuche mit exponentiellem Backoff vorgesehen. Eine feste Zustellzeit wird nicht zugesagt.

    experiment.created

    Laboreintrag angelegt

    id, display_id, title, status, project_id, author_id, created_at

    experiment.status_changed

    Status geändert

    id, display_id, old_status, new_status, updated_at

    experiment.signed

    signed_at erstmals gesetzt

    id, display_id, signed_by, signed_at

    sample.created

    Probe angelegt

    id, name, type, status, storage_location_id, created_at

    sample.status_changed

    Probenstatus geändert

    id, name, old_status, new_status, updated_at

    stock_movement.created

    Bewegung angelegt

    id, stock_item_id, movement_type, quantity_delta, quantity_after, unit, reason, …

    equipment.usage.started

    Nutzung begonnen

    session_id, equipment_id, user_id, started_at, purpose, project_id, entry_id, settings

    equipment.usage.completed

    Nutzung beendet

    session_id, equipment_id, started_at, ended_at, duration_minutes, auto_closed, …

    equipment.maintenance.completed

    Wartung abgeschlossen

    log_id, equipment_id, …

    Ereignisfilter

    experiment.* · sample.* · stock_movement.* · equipment.* · *

    Zustellformat

    id bezeichnet die Zustellung, event den Ereignistyp und data die Ereignisdaten. Empfänger müssen doppelte Zustellungen anhand der Zustell-ID abfangen. Das Beispiel zeigt eine Signatur eines Laboreintrags; IDs und Zeitpunkte sind Beispieldaten.

    JSON
    {
      "id": "11111111-1111-4111-8111-111111111111",
      "event": "experiment.signed",
      "org_id": "22222222-2222-4222-8222-222222222222",
      "delivered_at": "2026-10-07T03:00:00Z",
      "data": {
        "id": "33333333-3333-4333-8333-333333333333",
        "display_id": "EXP-0421",
        "signed_by": "44444444-4444-4444-8444-444444444444",
        "signed_at": "2026-10-07T02:59:58Z"
      }
    }

    Webhook-Signatur verifizieren

    X-LabNote-Signature enthält t=<Unix-Sekunden>,v1=<Hex-HMAC>. Signiert werden die unveränderten Bytes von <timestamp>.<raw body> mit HMAC-SHA256. Bei Secret-Rotation können zwei v1-Werte erscheinen: Ein gültiger Vergleich genügt. JSON erst nach der Prüfung parsen. Die Beispiele berücksichtigen mehrere Signaturen, fehlerhafte Header und eine Zeittoleranz von ±300 Sekunden.

    Node.js
    import { createHmac, timingSafeEqual } from "node:crypto";
    
    export function verifyLabnoteSignature(rawBody, header, secret,
                                          toleranceSeconds = 300) {
      if (typeof header !== "string") return false;
      const parts = header.split(",").map(p => p.trim());
      const timestamps = parts.filter(p => p.startsWith("t="));
      const signatures = parts.filter(p => p.startsWith("v1="))
        .map(p => p.slice(3));
      if (timestamps.length !== 1 || !signatures.length) return false;
      const stamp = timestamps[0].slice(2);
      if (!/^[0-9]+$/.test(stamp)) return false;
      const ts = Number(stamp);
      if (!Number.isSafeInteger(ts) ||
          Math.abs(Date.now() / 1000 - ts) > toleranceSeconds) return false;
      const expected = createHmac("sha256", secret)
        .update(stamp + ".").update(rawBody).digest();
      return signatures.some(sig => {
        if (!/^[a-f0-9]{64}$/i.test(sig)) return false;
        return timingSafeEqual(Buffer.from(sig, "hex"), expected);
      });
    }
    Python
    import hmac, hashlib, time, re
    
    def verify_labnote_signature(raw_body: bytes, header: str,
                                 secret: str, tolerance: int = 300) -> bool:
        if not isinstance(header, str):
            return False
        parts = [p.strip() for p in header.split(",")]
        stamps = [p[2:] for p in parts if p.startswith("t=")]
        sigs = [p[3:] for p in parts if p.startswith("v1=")]
        if len(stamps) != 1 or not re.fullmatch(r"[0-9]{1,16}", stamps[0]):
            return False
        if abs(time.time() - int(stamps[0])) > tolerance:
            return False
        expected = hmac.new(secret.encode(), stamps[0].encode() + b"." +
                            raw_body, hashlib.sha256).hexdigest()
        return any(re.fullmatch(r"[a-fA-F0-9]{64}", sig) and
                   hmac.compare_digest(expected, sig.lower()) for sig in sigs)

    Prüfung beim Empfänger

    1. Zeitstempel prüfen; alte und zu weit in der Zukunft liegende Zustellungen ablehnen.
    2. Unveränderten Body signieren und jede passende v1-Signatur konstantzeitlich vergleichen.
    3. Innerhalb des Zustell-Timeouts von 15 Sekunden mit 2xx antworten.
    4. Zustell-ID deduplizieren und erst danach fachlich verarbeiten.

    Eingehende Webhooks

    Separater POST-Endpunkt: /inbound-webhook/{org_id}/{slug}, nicht unter /api-v1/v1. Authentifizierung über das konfigurierte HMAC-Secret. Der Ereignistyp stammt aus event oder X-LabNote-Event, nicht aus event_type. Maximale Nachrichtengröße: 1.000.000 Bytes. HTTP 202 bestätigt Speicherung bzw. optionales Einreihen eines Jobs, nicht den erfolgreichen fachlichen Import. Nicht erlaubte Ereignisse erhalten ebenfalls 202 mit status: ignored. Ungültige Signaturen und unbekannte oder inaktive Endpunkte liefern einheitlich 404. external_id allein ist kein Deduplizierungsschlüssel; bei Wiederholungen denselben Idempotency-Key verwenden.

    Node.js
    // Node.js: sign the exact body sent to the receiver
    import { createHmac, randomUUID } from "node:crypto";
    const url = "https://vilasdqkwlszlulteqrb.supabase.co/functions/v1/inbound-webhook/<org-uuid>/<slug>";
    const body = JSON.stringify({ event: "instrument.measurement_recorded",
      payload: { sample_code: "S-001", value: 12.3, unit: "mg/L" } });
    const stamp = String(Math.floor(Date.now() / 1000));
    const sig = createHmac("sha256", process.env.LABNOTE_WEBHOOK_SECRET)
      .update(stamp + "." + body).digest("hex");
    const key = randomUUID(); // retain this key and body for retries
    const response = await fetch(url, { method: "POST", headers: {
      "Content-Type": "application/json",
      "X-LabNote-Signature": "t=" + stamp + ",v1=" + sig,
      "Idempotency-Key": key
    }, body });
    console.log(response.status);

    Hintergrundjobs

    POST /v1/jobs erwartet job_type und optional input und antwortet mit 202 und data. GET liest den Status. Die Typen audit_export, biosafety_export, gdpr_export und org_data_export erfordern eine Administratorrolle. Andere zugelassene Typen sind equipment_lifecycle_pdf, maintenance_report_pdf, equipment_book_pdf, experiment_entry_pdf, risk_assessment_pdf und box_layout_pdf. Nutzereigene Jobs sind für den Ersteller sichtbar, organisationsweite Einsicht für Administratoren. Annahme eines Jobs garantiert weder erfolgreichen Abschluss noch einen bestimmten Dateivertrag; input hängt vom jeweiligen Job ab.

    bash
    # Read existing jobs; creation needs job-specific input
    curl -sS "$LABNOTE_API/v1/jobs?limit=20" \
      -H "Authorization: Bearer $LABNOTE_API_KEY"
    
    # POST body format (replace input with the job's required fields):
    # {"job_type":"experiment_entry_pdf","input":{"entry_id":"<entry-uuid>"}}
    # Poll an accepted job:
    curl -sS "$LABNOTE_API/v1/jobs/<job-uuid>" \
      -H "Authorization: Bearer $LABNOTE_API_KEY"

    Benachrichtigungen

    POST benötigt write und adressiert Organisationsmitglieder über user_id, user_ids oder role. GET mit read liefert Benachrichtigungen des dem Token zugeordneten Nutzers; dies ist keine organisationsweite Abfrage. Zulässige Links beginnen mit http:// oder https://. Bei begrenzten Empfängerlisten oder Ergebnissen die Warning-Header und Pagination-Angaben auswerten.

    bash
    curl -sS -X POST "$LABNOTE_API/v1/notifications" \
      -H "Authorization: Bearer $LABNOTE_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: <stable-unique-request-key>" \
      -d '{"title":"K-04 temperature alert","body":"12.3 °C; limit 8 °C",
           "notification_type":"temperature_alarm","role":"admin"}'

    Limits und Wiederholungen

    REST-Standardlimits im Code: 120 Anfragen/Minute und 5.000/Stunde pro Token; die Bereitstellung kann andere Werte konfigurieren. Bei diesem Token-Limit liefert 429 aktuell Retry-After: 60, nicht die exakte Restdauer des Stundenfensters. Weitere routenspezifische Limits existieren. Idempotency-Key ist optional und nicht auf allen Schreibpfaden ausgewertet: unterstützt bei generischem CRUD, Jobs, Benachrichtigungen, Geräte-Unterressourcen-POST; nicht pauschal bei Connector-POST, Remote-Zugriff, Messdaten-Ingest oder Geräte-Unterressourcen-PATCH/PUT. Ein Replay behält den ursprünglichen Statuscode und setzt Idempotent-Replay: true; Standardaufbewahrung 24 Stunden.

    Generische Listen: limit (Standard 50, max. 500), offset, order_by, order=asc|desc und erlaubte Gleichheitsfilter. Geräte-Unterressourcen: Standard 100, max. 500, from/to. Audit: from/to-Zeitfilter, Standard 100, max. 1.000; next_cursor liefert since_created_at und since_id als nächste Query-Parameter. Jobs: Standard 50, max. 200; cursor ist der Zeitstempel enqueued_at aus next_cursor. Laborergebnisse sind auf 2.000 Zeilen und vorgeschaltete Probenabfragen auf 1.000 begrenzt; Warning-Header beachten. Nicht alle Listen besitzen offset oder einen Cursor.

    HTTP
    # Generic list
    GET /v1/samples?limit=50&offset=0&order_by=created_at&order=desc
    
    # Audit: use BOTH fields returned in next_cursor
    GET /v1/audit?since_created_at=<ISO-timestamp>&since_id=<uuid>&limit=100
    
    # Jobs: cursor is an enqueued_at timestamp
    GET /v1/jobs?cursor=<ISO-timestamp>&limit=50

    Testmodus und Fehler

    X-Test-Mode: true wird ausschließlich im generischen CRUD-Handler ausgewertet. Es prüft dort Anmeldung und vorgelagerte Schreibberechtigungen und gibt would_apply zurück, überspringt jedoch die eigentliche Feldvalidierung und Datensatzänderung. Es ist kein allgemeiner Dry-Run für Jobs, Webhooks oder Messdaten und kein vollständiger Schreibtest. Für echte Integrationstests eine isolierte Testorganisation verwenden. Unterschiedliche Fehlerformate auswerten; X-Request-Id ist für api-v1 nicht generell implementiert.

    JSON
    // Most REST route errors
    {"error":{"code":"insufficient_scope","message":"Token lacks 'read' scope."}}
    
    // Shared idempotency conflict (different shape)
    {"data":null,"error":"idempotency_key_conflict","meta":{"message":"Same Idempotency-Key used with a different payload."}}

    OAuth für Drittanbieter

    Die separaten Funktionen oauth-authorize und oauth-token implementieren einen Authorization-Code-Flow für registrierte Clients mit Client-Secret und rotierenden Refresh-Tokens. Das ist nicht die MCP-Anmeldung. Die Zustimmung bindet den Zugriff an die gewählte Organisation; admin und audit:read werden bei Nutzern ohne Administratorrolle entfernt. Einen öffentlichen PKCE-Flow oder eine allgemeine Freischaltung für beliebige Clients sagt diese Dokumentation nicht zu; Clientregistrierung und Bereitstellung müssen vor einer Integration geklärt werden.

    HTTP
    # Separate REST OAuth functions (registered confidential clients)
    https://vilasdqkwlszlulteqrb.supabase.co/functions/v1/oauth-authorize
    https://vilasdqkwlszlulteqrb.supabase.co/functions/v1/oauth-token
    
    # Not interchangeable with the MCP Supabase OAuth issuer.

    MCP: separater Lesezugriff

    Im MCP-Modul sind genau vier Werkzeuge definiert: whoami, list_experiments, list_samples und list_equipment. Die Listen sind nutzerbezogen und berechtigungsgefiltert; es gibt keine Schreib- oder Job-Werkzeuge. MCP verwendet Supabase-OAuth mit dem angemeldeten Nutzer, nicht den REST-API-Key. Der Funktionsname ist mcp, nicht mcp-server. Bereitstellung und erfolgreiche autorisierte Aufrufe sind hier nicht als live verifiziert ausgewiesen.

    whoamilist_experimentslist_sampleslist_equipment
    MCP
    # Defined MCP function; confirm deployment before connecting
    https://vilasdqkwlszlulteqrb.supabase.co/functions/v1/mcp
    
    # Discover with an OAuth-capable MCP client: tools/list
    # Read-only: whoami, list_experiments, list_samples, list_equipment
    # REST API keys are not MCP credentials.

    Integration abstimmen

    Diese Referenz ist öffentlich. Für eine konkrete Anbindung müssen Organisation, Berechtigungen, Clientregistrierung und Testumgebung geklärt sein.