Zum Inhalt springen
    Entwickler-Dokumentation

    LabNote API & Webhooks

    REST-Endpunkte für QM- & ELN-Integrationen, OAuth 2.0, asynchrone Jobs, signierte Outbound-Webhooks und ein MCP-Server für KI-Agenten — alles auf einer Seite.

    Base URL

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

    Authentifizierung

    Erzeuge Org-API-Keys unter Einstellungen → API. Jeder Key trägt Scopes (read, write, audit:read, admin) und ist auf eine Organisation gebunden. Persönliche Access Tokens (PAT) benötigen zusätzlich ?org_id=<uuid> oder den Header X-Org-Id. Sende den Token als Bearer.

    curl
    # Base URL
    export LABNOTE_API="https://vilasdqkwlszlulteqrb.supabase.co/functions/v1/api-v1"
    
    curl "$LABNOTE_API/v1/whoami" \
      -H "Authorization: Bearer $LABNOTE_API_KEY"
    
    # Personal Access Token (PAT) — org must be selected explicitly:
    curl "$LABNOTE_API/v1/experiments?limit=20" \
      -H "Authorization: Bearer $LABNOTE_PAT" \
      -H "X-Org-Id: <org-uuid>"

    Ressourcen-Übersicht

    Stabile v1-Endpunkte. Base-URL siehe oben; vollständiger Vertrag in der OpenAPI-Spec.

    EndpointBeschreibung
    GET /v1/whoamiToken introspection (org_id, scopes, identity)
    GET /v1/experiments · /{id}Experiments + entries (read)
    GET /v1/projects · /{id}Projects
    GET POST /v1/samples · /{id}Samples (list, create, get)
    GET /v1/equipment · /{id}Equipment master data
    GET /v1/equipment/{id}/usageUsage sessions (paginated, from/to)
    GET /v1/equipment/{id}/maintenanceMaintenance logs
    GET /v1/equipment/{id}/calibrationsCalibration records
    GET /v1/equipment/{id}/qualificationsIQ/OQ/PQ qualifications
    GET POST PATCH DELETE /v1/equipment/{id}/remoteRemote-access endpoint of the device (shows the remote button in LabNote)
    GET /v1/reagents · /v1/reagent-stock · /v1/stock-itemsReagent catalog & stock
    GET /v1/stock-movements · /v1/inventoriesMovements & inventories
    GET /v1/devices · /v1/resultsInstruments & instrument results
    GET /v1/auditAudit trail (cursor pagination)
    GET POST /v1/jobs · /{id}Async background jobs (enqueue + poll)
    GET POST /v1/notificationsPush/list in-app notifications

    Outbound Event-Katalog

    LabNote sendet HMAC-signierte Webhooks an konfigurierte Endpunkte, sobald ein passendes Event ausgelöst wird. Subscriptions akzeptieren exakte Event-Namen und Wildcards.

    EventTriggerPayload fields
    experiment.createdINSERT on experiment_entriesid, display_id, title, status, project_id, author_id, created_at
    experiment.status_changedUPDATE of statusid, display_id, old_status, new_status, updated_at
    experiment.signedfirst time signed_at becomes non-nullid, display_id, signed_by, signed_at
    sample.createdINSERT on samplesid, name, type, status, storage_location_id, created_at
    sample.status_changedUPDATE of statusid, name, old_status, new_status, updated_at
    stock_movement.createdINSERT on reagent_stock_movementsid, stock_item_id, movement_type, quantity_delta, quantity_after, unit, reason, …
    equipment.usage.startedINSERT on equipment_usage_sessionssession_id, equipment_id, user_id, started_at, purpose, project_id, entry_id, settings
    equipment.usage.completedended_at set (manual or auto-close)session_id, equipment_id, started_at, ended_at, duration_minutes, auto_closed, …
    equipment.maintenance.completedmaintenance log with completed_datelog_id, equipment_id, kind, completed_date, completed_by, next_due_date

    Unterstützte Wildcards

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

    Envelope

    Jedes Delivery hat dieselbe äußere Struktur. Die eigentliche Nutzlast steckt in data.

    json
    {
      "id": "9c0e…-delivery-uuid",
      "event": "experiment.signed",
      "org_id": "f4a1…",
      "delivered_at": "2026-06-19T19:00:00Z",
      "data": {
        "id": "…",
        "display_id": "EXP-0421",
        "signed_by": "…",
        "signed_at": "2026-06-19T18:59:58Z"
      }
    }

    HMAC-Signatur prüfen

    Header X-Labnote-Signature enthält t=<ts>,v1=<hex>. Signierte Bytes = `${timestamp}.${raw_body}`. Während einer Secret-Rotation können zwei v1=-Werte vorhanden sein – akzeptiere beide.

    TypeScript
    import { createHmac, timingSafeEqual } from "node:crypto";
    
    export function verifyLabnoteSignature(
      rawBody: string,
      header: string | null,
      secret: string,
      toleranceSeconds = 300,
    ): boolean {
      if (!header) return false;
      const parts = Object.fromEntries(
        header.split(",").map((p) => p.trim().split("=", 2)),
      );
      const ts = Number(parts.t);
      if (!ts || Math.abs(Date.now() / 1000 - ts) > toleranceSeconds) return false;
    
      const expected = createHmac("sha256", secret)
        .update(`${ts}.${rawBody}`)
        .digest("hex");
    
      const got = Buffer.from(parts.v1 ?? "", "hex");
      const exp = Buffer.from(expected, "hex");
      return got.length === exp.length && timingSafeEqual(got, exp);
    }
    Python
    import hmac, hashlib, time
    
    def verify_labnote_signature(raw_body: bytes, header: str, secret: str,
                                 tolerance: int = 300) -> bool:
        parts = dict(p.strip().split("=", 1) for p in header.split(","))
        ts = int(parts.get("t", "0"))
        if abs(time.time() - ts) > tolerance:
            return False
        expected = hmac.new(
            secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256
        ).hexdigest()
        return hmac.compare_digest(expected, parts.get("v1", ""))

    Empfänger-Checkliste

    1. X-Labnote-Timestamp lesen und Deliveries älter als 5 Minuten ablehnen.
    2. HMAC_SHA256(secret, `${timestamp}.${raw_body}`) konstant-zeitlich vergleichen.
    3. Innerhalb von 15 s mit 2xx antworten – sonst Retry mit Backoff, dann DLQ.
    4. Idempotent auf id (Delivery-UUID) verarbeiten.

    Inbound-Webhooks

    Empfange externe Events. Konfiguriere Slug, Event-Whitelist und HMAC-Secret unter Einstellungen → Integrationen → Inbound. Optional kann pro Endpunkt automatisch ein Background-Job eingereiht werden.

    curl
    # Inbound delivery — org_id is part of the path
    curl -X POST "https://vilasdqkwlszlulteqrb.supabase.co/functions/v1/inbound-webhook/<org-uuid>/<slug>" \
      -H "Content-Type: application/json" \
      -H "X-Labnote-Timestamp: $(date +%s)" \
      -H "X-Labnote-Signature: t=$(date +%s),v1=<hex>" \
      -H "Idempotency-Key: $(uuidgen)" \
      --data '{
        "event_type": "instrument.measurement_recorded",
        "occurred_at": "2026-06-19T20:15:00Z",
        "external_id": "HPLC-A-001#run-1",
        "payload": { "instrument_serial": "HPLC-A-001", "results": [] }
      }'

    Asynchrone Jobs

    Lang laufende Operationen (Audit-/GDPR-/Org-Exports, Equipment-Lifecycle-, Maintenance-, Experiment- und Risk-Assessment-PDFs) werden über die Jobs-Queue gestartet und gepollt.

    curl
    # Enqueue a background job (writes require Idempotency-Key)
    curl -X POST "$LABNOTE_API/v1/jobs" \
      -H "Authorization: Bearer $LABNOTE_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d '{"job_type":"report_export","payload":{"report_id":"…"}}'
    
    # Poll
    curl "$LABNOTE_API/v1/jobs/<job-id>" \
      -H "Authorization: Bearer $LABNOTE_API_KEY"

    Notifications

    Externe Systeme (Monitoring, IoT-Sensoren, Ticket-Gateways) pushen In-App-Benachrichtigungen an einzelne Nutzer, Nutzerlisten oder Rollen. Benötigt write-Scope; GET liefert die eigenen Notifications des aufrufenden PAT-Nutzers.

    curl
    # Push a notification to all admins of the org
    # Target: user_id (single) | user_ids (array) | role (string)
    curl -X POST "$LABNOTE_API/v1/notifications" \
      -H "Authorization: Bearer $LABNOTE_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d '{
        "title": "Temperaturalarm Kühlschrank K-04",
        "body": "12.3 °C um 09:12 (Soll ≤ 8 °C)",
        "notification_type": "temperature_alarm",
        "link_url": "https://monitor.example.com/alerts/4711",
        "role": "admin"
      }'
    
    # List your own notifications (PAT only)
    curl "$LABNOTE_API/v1/notifications?limit=20" \
      -H "Authorization: Bearer $LABNOTE_PAT" \
      -H "X-Org-Id: <org-uuid>"

    Rate-Limits, Idempotenz & Pagination

    Schutzmechanismen und Konventionen, die für alle v1-Endpunkte gelten.

    Rate limits

    120 req/min · 5000 req/h per token. 429 → Retry-After.

    Idempotency

    All writes accept Idempotency-Key. Replays return Idempotent-Replay: true.

    Pagination

    offset+limit · cursor (cursor bzw. since_created_at+since_id) auf /v1/audit & /v1/jobs.

    bash
    # Rate limits per token: 120 req/min, 5000 req/h
    # On 429 the response carries Retry-After (seconds).
    
    # Idempotency on every write:
    #   Idempotency-Key: <uuid>
    # Replays return: Idempotent-Replay: true
    
    # Pagination:
    #   - List endpoints:   ?limit=100&offset=0  (max limit 500)
    #   - /v1/audit, /v1/jobs use cursor pagination:
    #       ?cursor=<opaque>   OR   ?since_created_at=…&since_id=…

    OAuth 2.0

    Drittanbieter-Apps integrieren via Authorization-Code-Flow mit Refresh-Token-Rotation. Endpunkte: /oauth-authorize und /oauth-token.

    bash
    # 1. Redirect the user to the authorize endpoint:
    https://vilasdqkwlszlulteqrb.supabase.co/functions/v1/oauth-authorize
      ?response_type=code
      &client_id=<your-client-id>
      &redirect_uri=<your-callback>
      &scope=read%20write
      &state=<csrf>
    
    # 2. Exchange the code for tokens:
    curl -X POST "https://vilasdqkwlszlulteqrb.supabase.co/functions/v1/oauth-token" \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d "grant_type=authorization_code" \
      -d "code=<code>" \
      -d "client_id=<your-client-id>" \
      -d "client_secret=<your-client-secret>" \
      -d "redirect_uri=<your-callback>"
    
    # 3. Refresh (refresh tokens rotate on every use):
    curl -X POST "https://vilasdqkwlszlulteqrb.supabase.co/functions/v1/oauth-token" \
      -d "grant_type=refresh_token" \
      -d "refresh_token=<rt>" \
      -d "client_id=<your-client-id>" \
      -d "client_secret=<your-client-secret>"

    MCP-Server für KI-Agenten

    Streamable HTTP / JSON-RPC. Authentifizierung wie REST (Bearer API-Key). Verfügbare Tools:

    whoami
    list_experiments
    get_experiment
    list_samples
    list_equipment
    get_equipment
    list_reagent_stock
    list_jobs
    get_job
    enqueue_job
    JSON-RPC
    POST https://vilasdqkwlszlulteqrb.supabase.co/functions/v1/mcp-server
    Authorization: Bearer $LABNOTE_API_KEY
    Content-Type: application/json
    
    {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "tools/call",
      "params": {
        "name": "list_experiments",
        "arguments": { "limit": 5 }
      }
    }

    Diese Dokumentation teilen

    Direkter Link zum Teilen mit Partnern oder Kunden – kein Login nötig.

    https://labnote-light.com/api-docs