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-v1Authentifizierung
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.
# 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.
| Endpoint | Beschreibung |
|---|---|
| GET /v1/whoami | Token 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}/usage | Usage sessions (paginated, from/to) |
| GET /v1/equipment/{id}/maintenance | Maintenance logs |
| GET /v1/equipment/{id}/calibrations | Calibration records |
| GET /v1/equipment/{id}/qualifications | IQ/OQ/PQ qualifications |
| GET POST PATCH DELETE /v1/equipment/{id}/remote | Remote-access endpoint of the device (shows the remote button in LabNote) |
| GET /v1/reagents · /v1/reagent-stock · /v1/stock-items | Reagent catalog & stock |
| GET /v1/stock-movements · /v1/inventories | Movements & inventories |
| GET /v1/devices · /v1/results | Instruments & instrument results |
| GET /v1/audit | Audit trail (cursor pagination) |
| GET POST /v1/jobs · /{id} | Async background jobs (enqueue + poll) |
| GET POST /v1/notifications | Push/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.
| Event | Trigger | Payload fields |
|---|---|---|
| experiment.created | INSERT on experiment_entries | id, display_id, title, status, project_id, author_id, created_at |
| experiment.status_changed | UPDATE of status | id, display_id, old_status, new_status, updated_at |
| experiment.signed | first time signed_at becomes non-null | id, display_id, signed_by, signed_at |
| sample.created | INSERT on samples | id, name, type, status, storage_location_id, created_at |
| sample.status_changed | UPDATE of status | id, name, old_status, new_status, updated_at |
| stock_movement.created | INSERT on reagent_stock_movements | id, stock_item_id, movement_type, quantity_delta, quantity_after, unit, reason, … |
| equipment.usage.started | INSERT on equipment_usage_sessions | session_id, equipment_id, user_id, started_at, purpose, project_id, entry_id, settings |
| equipment.usage.completed | ended_at set (manual or auto-close) | session_id, equipment_id, started_at, ended_at, duration_minutes, auto_closed, … |
| equipment.maintenance.completed | maintenance log with completed_date | log_id, equipment_id, kind, completed_date, completed_by, next_due_date |
Unterstützte Wildcards
Envelope
Jedes Delivery hat dieselbe äußere Struktur. Die eigentliche Nutzlast steckt in data.
{
"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.
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);
}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
- X-Labnote-Timestamp lesen und Deliveries älter als 5 Minuten ablehnen.
- HMAC_SHA256(secret, `${timestamp}.${raw_body}`) konstant-zeitlich vergleichen.
- Innerhalb von 15 s mit 2xx antworten – sonst Retry mit Backoff, dann DLQ.
- 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.
# 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.
# 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.
# 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.
# 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.
# 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:
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