REST-API für Entwickler. Authentication, Endpoints, Webhooks, Universal-REST-Connector, Beispiele.
Version 0.5.0 · API v1 · Stand: 2026-04-24
| Umgebung | URL |
|---|---|
| Production | https://api.nemix.ainemix.de/api/v1 |
| Staging | https://api-staging.nemix.ainemix.de/api/v1 |
| Local Dev | http://localhost:3001/api/v1 |
Live-Swagger-UI: https://api.nemix.ainemix.de/docs
Raw-JSON: https://api.nemix.ainemix.de/openapi.json
2026-04-24T10:32:11.842Z)01H8X...) bzw. UUID v41234 = 12,34 €)Pfad-Versionierung: /api/v1/.... Breaking Changes ergeben /api/v2/.... Vorgängerversion läuft mindestens 12 Monate weiter, mit Sunset-Header und Doku.
Zwei Verfahren — wählen Sie eines pro Request:
Nach POST /auth/login setzt der Server ein Cookie nemix.session. Anschließend credentials: 'include'. CSRF-Token im Header X-CSRF-Token (aus /auth/csrf).
Authorization: Bearer nmx_live_aBcDeFg...
oder
X-API-Key: nmx_live_aBcDeFg...
Erzeugung & Verwaltung: ADMIN_GUIDE.md §5.
POST /api/v1/auth/login
Content-Type: application/json
{
"email": "anna@mueller-bau.de",
"password": "<password>"
}
Response 200:
{
"user": { "id": "user_42", "email": "anna@mueller-bau.de", "name": "Anna Müller" },
"tenants": [{ "id": "tenant_demo", "slug": "mueller-bau", "name": "Müller Bau GmbH", "role": "admin" }]
}
Cookie nemix.session ist gesetzt (HTTP-only, Secure, SameSite=Lax).
POST /api/v1/auth/logout
Response 204.
Multi-Tenant: jeder Request muss den Tenant identifizieren. Drei Wege (in Reihenfolge geprüft):
https://mueller-bau.nemix.ainemix.de/... → Slug = mueller-bauX-Tenant-ID: tenant_demo (für Multi-Tenant-Tools)Bei Konflikt: API-Key gewinnt > Header gewinnt > Subdomain.
Limits pro Tenant + Plan:
| Plan | Requests/min | KI-Aktionen/Tag | Bulk-Import/Tag |
|---|---|---|---|
| Starter | 60 | 100 | 10 |
| Professional | 600 | 1.000 | 100 |
| Business | 6.000 | 10.000 | 1.000 |
| Enterprise | 60.000 | fair-use | unlimited |
Über-Limit: HTTP 429 mit:
{
"error": "rate_limit_exceeded",
"retry_after_seconds": 12,
"limit": 60,
"window_seconds": 60
}
Header:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1714123876
Retry-After: 12
Cursor-basiert (für Stabilität bei großen, sich ändernden Listen).
GET /api/v1/customers?limit=50
Response:
{
"data": [{ "id": "...", "name": "..." }, ...],
"pageInfo": {
"hasNextPage": true,
"endCursor": "eyJpZCI6ICIwMUg4WC4uLiJ9"
},
"totalEstimate": 1247
}
Nächste Seite:
GET /api/v1/customers?limit=50&cursor=eyJpZCI6ICIwMUg4WC4uLiJ9
limit Default 25, max 200.
GET /api/v1/customers?status=active&country=DE
GET /api/v1/customers?createdAt[gte]=2026-01-01&createdAt[lt]=2026-02-01
Operatoren: eq, neq, gt, gte, lt, lte, in, nin, like, ilike, null.
GET /api/v1/customers?q=müller
Nutzt Meilisearch im Hintergrund — typo-tolerant.
GET /api/v1/customers?sort=createdAt:desc,name:asc
GET /api/v1/customers?fields=id,name,email
Reduziert Payload-Größe.
GET /api/v1/orders?embed=customer,items
Statt N+1 Calls vom Client.
| Modul | Pfad |
|---|---|
| Auth | /auth/* |
| Customers | /api/v1/customers |
| Orders | /api/v1/orders |
| Invoices | /api/v1/invoices |
| Products | /api/v1/products |
| Projects | /api/v1/projects |
| Workflows | /api/v1/workflows |
| AI Chat | /api/v1/ai/chat |
| AI Actions | /api/v1/ai-actions |
| Analytics | /api/v1/analytics |
| Integrations | /api/v1/integrations |
| Webhooks | /api/v1/webhooks |
| Audit-Log | /api/v1/audit |
| Admin | /admin/* (System-Admin only) |
| Health | /health |
GET /api/v1/customers?limit=25&q=müller
Authorization: Bearer nmx_live_...
{
"data": [
{
"id": "cust_01H8XABC",
"name": "Müller Bau GmbH",
"email": "info@mueller-bau.de",
"phone": "+49 89 1234567",
"vatId": "DE123456789",
"billingAddress": { "street": "Hauptstr. 5", "zip": "80331", "city": "München", "country": "DE" },
"tags": ["premium", "regional"],
"status": "active",
"createdAt": "2026-01-15T08:00:00.000Z",
"updatedAt": "2026-04-23T14:21:11.234Z"
}
],
"pageInfo": { "hasNextPage": false, "endCursor": null },
"totalEstimate": 1
}
GET /api/v1/customers/cust_01H8XABC
POST /api/v1/customers
Content-Type: application/json
{
"name": "Schneider Handel KG",
"email": "kontakt@schneider.de",
"phone": "+49 30 7654321",
"vatId": "DE987654321",
"billingAddress": {
"street": "Friedrichstr. 100",
"zip": "10117",
"city": "Berlin",
"country": "DE"
},
"tags": ["b2b"]
}
Response 201 mit dem angelegten Datensatz.
PATCH /api/v1/customers/cust_01H8XABC
Content-Type: application/json
{ "phone": "+49 89 9876543" }
PATCH überschreibt nur die mitgegebenen Felder. Für volle Ersetzung PUT.
DELETE /api/v1/customers/cust_01H8XABC
Response 204. Datensatz hat danach deletedAt gesetzt, ist über ?includeDeleted=true weiter abrufbar (nur Admin).
GET /api/v1/orders?status=in_progress&customerId=cust_01H8XABC&embed=items
POST /api/v1/orders
Content-Type: application/json
{
"customerId": "cust_01H8XABC",
"orderDate": "2026-04-24",
"expectedDelivery": "2026-05-02",
"paymentTerms": "net_14",
"items": [
{
"productId": "prod_001",
"quantity": 10,
"unitPriceNet": 4990,
"discountPercent": 5
},
{
"description": "Sondertext-Position",
"quantity": 1,
"unitPriceNet": 12000,
"vatRate": 19
}
],
"internalNote": "Eilauftrag — Liefertermin halten."
}
POST /api/v1/orders/order_01H8/transition
Content-Type: application/json
{ "to": "confirmed" }
Server prüft erlaubte Transitions. Bei Verstoß HTTP 422.
POST /api/v1/orders/order_01H8/invoice
Content-Type: application/json
{
"type": "full", // full | partial | final
"itemIds": null, // bei partial: Array von Item-IDs
"send": true,
"sendVia": "email" // email | peppol | pdf_only
}
Response 201 mit dem Invoice-Datensatz.
GET /api/v1/workflows
POST /api/v1/workflows
Content-Type: application/json
{
"name": "Premium-Kunde Onboarding",
"trigger": { "type": "customer.created", "filter": { "tags": { "contains": "premium" } } },
"actions": [
{ "type": "send_slack_message", "config": { "channel": "#vertrieb", "text": "Neuer Premium-Kunde: {{customer.name}}" } },
{ "type": "create_project", "config": { "templateId": "tpl_onboarding", "customerId": "{{customer.id}}" } }
],
"active": true
}
GET /api/v1/workflows/wf_01H8/runs?limit=20
{
"data": [
{ "id": "run_01H9", "status": "success", "startedAt": "...", "durationMs": 423, "actionsCompleted": 2 },
{ "id": "run_01H8", "status": "failed", "startedAt": "...", "error": { "step": 1, "code": "SLACK_AUTH_EXPIRED" } }
]
}
POST /api/v1/workflows/wf_01H8/trigger
Content-Type: application/json
{ "context": { "customerId": "cust_01H8XABC" } }
POST /api/v1/ai/chat
Content-Type: application/json
Accept: text/event-stream
{
"messages": [
{ "role": "user", "content": "Wie viel Umsatz hatten wir gestern?" }
],
"stage": "active",
"context": { "currentPage": "/dashboard" }
}
Response: Server-Sent-Events (data: {...}\n\n):
event: text
data: {"delta": "Gestern "}
event: text
data: {"delta": "hatten Sie 14.823,42 €..."}
event: tool_use
data: {"name": "query_data", "input": {...}}
event: tool_result
data: {"data": {...}, "auditId": "audit_..."}
event: done
data: {"usage": {"inputTokens": 1234, "outputTokens": 234}}
POST /api/v1/ai-actions/execute
Content-Type: application/json
{
"tool": "create_customer",
"input": {
"name": "Test GmbH",
"email": "test@test.de"
},
"stage": "active",
"userConfirmed": false
}
Response 200 (success):
{
"ok": true,
"data": { "id": "cust_01HX...", "name": "Test GmbH", ... },
"auditId": "audit_01H...",
"stage": "active",
"tool": "create_customer"
}
Response 200 (confirmation required):
{
"ok": false,
"requiresConfirmation": true,
"preview": {
"operation": "create",
"entity": "customer",
"after": { "name": "Test GmbH", "email": "test@test.de" },
"diff": [ { "field": "name", "from": null, "to": "Test GmbH" }, ... ]
},
"confirmationToken": "conf_01H..."
}
Re-Call mit userConfirmed: true und confirmationToken.
GET /api/v1/ai/usage?period=current_month
{
"period": "2026-04",
"actions": { "used": 312, "limit": 1000 },
"tokens": { "input": 1245678, "output": 234567 },
"costEur": 12.34,
"perTool": { "query_data": 145, "create_customer": 12, ... },
"perUser": { "user_42": 89, "user_43": 223 }
}
GET /api/v1/integrations
{
"connected": [
{ "id": "int_shopify_01", "type": "shopify", "name": "Hauptshop", "status": "active", "lastSyncAt": "..." }
],
"available": [
{ "type": "dhl", "name": "DHL Versenden", "category": "shipping" },
{ "type": "datev_unternehmen_online", "name": "DATEV UO", "category": "accounting" }
]
}
POST /api/v1/integrations/connect
Content-Type: application/json
{ "type": "shopify", "shopUrl": "mein-shop.myshopify.com" }
Response:
{ "authUrl": "https://mein-shop.myshopify.com/admin/oauth/authorize?..." }
Browser-Redirect → User autorisiert → Callback an /api/v1/integrations/callback?.... Server schließt Verbindung ab.
POST /api/v1/integrations/int_shopify_01/sync
Content-Type: application/json
{ "entities": ["products", "orders"] }
DELETE /api/v1/integrations/int_shopify_01
Externe Systeme schicken Events an Nemix.
POST /api/v1/webhooks/incoming/<webhookId>
X-Signature: sha256=...
Content-Type: application/json
{ "event": "order.created", "data": { ... } }
webhookId wird beim Anlegen einer Integration generiert. Signature-Verifikation per HMAC-SHA256 mit Shared Secret.
POST /api/v1/integrations/webhooks
Content-Type: application/json
{
"name": "n8n LV-Auswerter",
"secret": null // null = generieren
}
Response:
{
"id": "wh_01H...",
"url": "https://api.nemix.ainemix.de/api/v1/webhooks/incoming/wh_01H...",
"secret": "whsec_...",
"createdAt": "..."
}
Nemix schickt Events an Ihre Endpoints.
POST /api/v1/webhooks/subscriptions
Content-Type: application/json
{
"url": "https://meinsystem.de/nemix-events",
"events": ["customer.created", "order.confirmed", "invoice.paid"],
"secret": null,
"active": true
}
{
"id": "evt_01H...",
"type": "order.confirmed",
"tenantId": "tenant_demo",
"createdAt": "2026-04-24T10:32:11.842Z",
"data": { ... },
"previousAttributes": { ... }
}
Header bei Versand:
X-Nemix-Signature: sha256=<hex>
X-Nemix-Event-Id: evt_01H...
X-Nemix-Event-Type: order.confirmed
X-Nemix-Tenant-Id: tenant_demo
Nicht-2xx-Response → Retry mit Exponential-Backoff: 30s, 5min, 30min, 2h, 6h, 24h. Nach 6 Fehlversuchen wird Subscription auf failing gesetzt.
customer.created
customer.updated
customer.deleted
order.created
order.confirmed
order.completed
order.cancelled
invoice.created
invoice.sent
invoice.paid
invoice.overdue
product.low_stock
project.phase_completed
workflow.executed
ai.tool.executed
auth.user.invited
Bindet jede REST-API ohne Code an. Endpoints werden Tools im Workflow-Builder.
POST /api/v1/integrations/custom
Content-Type: application/json
{
"name": "Fremdsystem-API",
"baseUrl": "https://api.fremd.de/v2",
"auth": {
"type": "bearer",
"token": "abc123..."
},
"openApiSpec": null,
"endpoints": [
{
"id": "list_articles",
"method": "GET",
"path": "/articles",
"description": "Liste Artikel",
"queryParams": [
{ "name": "page", "type": "number" },
{ "name": "limit", "type": "number" }
],
"responseMapping": {
"fromField": "items",
"toEntity": "external_article",
"fields": {
"id": "$.id",
"title": "$.title",
"price": "$.price_cents"
}
}
},
{
"id": "create_article",
"method": "POST",
"path": "/articles",
"bodySchema": {
"type": "object",
"properties": {
"title": { "type": "string" },
"price": { "type": "number" }
}
}
}
]
}
POST /api/v1/integrations/custom
Content-Type: application/json
{
"name": "Fremdsystem-API",
"baseUrl": "https://api.fremd.de/v2",
"auth": { "type": "api_key", "header": "X-Api-Key", "value": "..." },
"openApiUrl": "https://api.fremd.de/v2/openapi.json"
}
Server lädt Spec, parst alle Endpoints, AiNemix schlägt Field-Mapping vor → Bestätigung im UI.
POST /api/v1/integrations/custom/<connectorId>/call
Content-Type: application/json
{
"endpointId": "list_articles",
"params": { "page": 1, "limit": 50 }
}
Response: gemappte Daten in Nemix-Format.
| Type | Felder |
|---|---|
none |
- |
api_key |
header (z. B. X-Api-Key), value |
bearer |
token |
basic |
username, password |
oauth2_cc |
tokenUrl, clientId, clientSecret, scope |
oauth2_ac |
authorizeUrl, tokenUrl, clientId, clientSecret, redirectUri |
OAuth2 Auth-Code Flow: Server hostet die Callback-URL, Token wird verschlüsselt im Tenant-Schema gespeichert (nie ins Frontend).
| Status | Bedeutung |
|---|---|
| 200 | OK |
| 201 | Created |
| 204 | No Content (Delete OK) |
| 400 | Bad Request (Validation, falsche Parameter) |
| 401 | Unauthorized (kein/ungültiger Auth-Header) |
| 403 | Forbidden (auth ok, aber nicht berechtigt) |
| 404 | Not Found |
| 409 | Conflict (z. B. doppelter SKU) |
| 422 | Unprocessable Entity (Business-Rule verletzt) |
| 429 | Too Many Requests (Rate-Limit) |
| 500 | Internal Server Error |
| 502 | Bad Gateway (Upstream-Service down) |
| 503 | Service Unavailable (Wartung) |
{
"error": {
"code": "VALIDATION_ERROR",
"message": "email is required",
"details": [
{ "field": "email", "issue": "missing" }
],
"requestId": "req_01H..."
}
}
| Code | HTTP | Bedeutung |
|---|---|---|
VALIDATION_ERROR |
400 | Eingabe entspricht nicht dem Schema |
INVALID_AUTH |
401 | Kein/falscher Token |
EXPIRED_AUTH |
401 | Token abgelaufen |
MISSING_TENANT |
400 | Tenant-Kontext nicht ableitbar |
TENANT_NOT_FOUND |
404 | Slug existiert nicht |
TENANT_SUSPENDED |
403 | Tenant deaktiviert (Zahlung, Verstoß) |
PERMISSION_DENIED |
403 | Rolle/Permission unzureichend |
PLAN_NOT_ALLOWED |
403 | Feature nicht im aktuellen Plan |
RATE_LIMIT_EXCEEDED |
429 | Zu viele Requests |
AI_BUDGET_EXCEEDED |
429 | AI-Aktionen-Limit erreicht |
RESOURCE_NOT_FOUND |
404 | Datensatz existiert nicht |
RESOURCE_CONFLICT |
409 | Eindeutigkeit verletzt |
INVALID_STATE_TRANSITION |
422 | z. B. draft → completed direkt nicht erlaubt |
BUSINESS_RULE_VIOLATION |
422 | branchen-/buchführungs-spezifische Regel |
CONFIRMATION_REQUIRED |
200 | (AI) destruktive Aktion braucht Bestätigung |
INTEGRATION_AUTH_FAILED |
502 | OAuth-Token abgelaufen |
INTEGRATION_TIMEOUT |
504 | Externer Service zu langsam |
# Kunden listen
curl -X GET "https://api.nemix.ainemix.de/api/v1/customers?limit=10" \
-H "Authorization: Bearer $NEMIX_API_KEY" \
-H "Accept: application/json"
# Kunden anlegen
curl -X POST "https://api.nemix.ainemix.de/api/v1/customers" \
-H "Authorization: Bearer $NEMIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Test GmbH",
"email": "test@test.de"
}'
# Auftrag bestätigen
curl -X POST "https://api.nemix.ainemix.de/api/v1/orders/order_01H8/transition" \
-H "Authorization: Bearer $NEMIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to":"confirmed"}'
const NEMIX_API = 'https://api.nemix.ainemix.de/api/v1'
const API_KEY = process.env.NEMIX_API_KEY
async function listCustomers(query = '') {
const url = new URL(`${NEMIX_API}/customers`)
if (query) url.searchParams.set('q', query)
url.searchParams.set('limit', '50')
const res = await fetch(url, {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Accept': 'application/json',
},
})
if (!res.ok) {
const err = await res.json()
throw new Error(`${err.error.code}: ${err.error.message}`)
}
return await res.json()
}
async function createCustomer(data) {
const res = await fetch(`${NEMIX_API}/customers`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
return res.json()
}
// Streaming AI Chat
async function chat(message) {
const res = await fetch(`${NEMIX_API}/ai/chat`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
'Accept': 'text/event-stream',
},
body: JSON.stringify({
messages: [{ role: 'user', content: message }],
stage: 'active',
}),
})
const reader = res.body.getReader()
const decoder = new TextDecoder()
while (true) {
const { done, value } = await reader.read()
if (done) break
process.stdout.write(decoder.decode(value))
}
}
import os
import requests
NEMIX_API = "https://api.nemix.ainemix.de/api/v1"
API_KEY = os.environ["NEMIX_API_KEY"]
session = requests.Session()
session.headers.update({
"Authorization": f"Bearer {API_KEY}",
"Accept": "application/json",
})
def list_customers(query: str | None = None, limit: int = 50) -> list[dict]:
params = {"limit": limit}
if query:
params["q"] = query
r = session.get(f"{NEMIX_API}/customers", params=params)
r.raise_for_status()
return r.json()["data"]
def create_customer(name: str, email: str, **kwargs) -> dict:
payload = {"name": name, "email": email, **kwargs}
r = session.post(f"{NEMIX_API}/customers", json=payload)
r.raise_for_status()
return r.json()
def execute_ai_tool(tool: str, input_data: dict, stage: str = "active") -> dict:
r = session.post(
f"{NEMIX_API}/ai-actions/execute",
json={"tool": tool, "input": input_data, "stage": stage},
)
r.raise_for_status()
return r.json()
# Beispiel: Pagination
def all_customers():
cursor = None
while True:
params = {"limit": 200}
if cursor:
params["cursor"] = cursor
r = session.get(f"{NEMIX_API}/customers", params=params)
r.raise_for_status()
body = r.json()
for c in body["data"]:
yield c
if not body["pageInfo"]["hasNextPage"]:
break
cursor = body["pageInfo"]["endCursor"]
Beispiel: Anbindung an einen fiktiven Lieferanten-API.
# 1. Connector anlegen
import requests, os
connector = requests.post(
"https://api.nemix.ainemix.de/api/v1/integrations/custom",
headers={"Authorization": f"Bearer {os.environ['NEMIX_API_KEY']}"},
json={
"name": "Lieferanten-Bestellsystem",
"baseUrl": "https://api.lieferant.de/v1",
"auth": {"type": "bearer", "token": os.environ["LIEFERANT_TOKEN"]},
"endpoints": [
{
"id": "create_purchase_order",
"method": "POST",
"path": "/orders",
"bodySchema": {
"type": "object",
"required": ["sku", "qty"],
"properties": {
"sku": {"type": "string"},
"qty": {"type": "integer"},
},
},
}
],
},
).json()
connector_id = connector["id"]
# 2. Endpoint nutzen — entweder direkt
res = requests.post(
f"https://api.nemix.ainemix.de/api/v1/integrations/custom/{connector_id}/call",
headers={"Authorization": f"Bearer {os.environ['NEMIX_API_KEY']}"},
json={
"endpointId": "create_purchase_order",
"params": {"sku": "ART-001", "qty": 50},
},
)
# 3. Oder per Workflow: in Workflow-Builder erscheint
# nun "Lieferanten-Bestellsystem → create_purchase_order" als Aktion.
Versionshistorie: CHANGELOG.md. Breaking Changes werden mindestens 8 Wochen vorher per Mail an alle API-Key-Owner kommuniziert.
https://status.nemix.ainemix.deGET /health → { "status": "ok", "version": "0.5.0" }GET /api/v1/invoices?status=overdue&limit=50
{
"data": [
{
"id": "inv_01H8XABC",
"number": "2026-0042",
"customerId": "cust_01H...",
"amountNetCents": 100000,
"amountGrossCents": 119000,
"vatRate": 19,
"issueDate": "2026-04-10",
"dueDate": "2026-04-24",
"status": "overdue",
"items": [ ... ],
"xmlFormat": "xrechnung_3.0",
"peppolSent": true
}
]
}
POST /api/v1/invoices
Content-Type: application/json
{
"customerId": "cust_01H...",
"issueDate": "2026-04-24",
"dueDate": "2026-05-08",
"items": [
{ "description": "Beratungsstunde", "quantity": 10, "unitPriceNet": 9000, "vatRate": 19 }
],
"paymentTerms": "net_14",
"reverseCharge": false,
"leitwegId": null
}
POST /api/v1/invoices/inv_01H8XABC/send
Content-Type: application/json
{
"via": "peppol",
"recipient": null
}
POST /api/v1/invoices/inv_01H8XABC/payment
Content-Type: application/json
{
"amountCents": 119000,
"date": "2026-04-22",
"method": "bank_transfer",
"reference": "Buchungstext aus Bank"
}
POST /api/v1/invoices/inv_01H8XABC/cancel
Content-Type: application/json
{ "reason": "Fehlerhafte Position", "issueCorrective": true }
Erzeugt Storno-Rechnung + Korrektur-Rechnung, GoBD-konform.
GET /api/v1/invoices/inv_01H8XABC/pdf
Accept: application/pdf
Zusätzlich:
- ?format=xrechnung — nur XML
- ?format=zugferd — PDF mit eingebettetem XML
GET /api/v1/products?category=material&lowStock=true&limit=100
POST /api/v1/products
Content-Type: application/json
{
"sku": "ART-001",
"name": "Zementsack 25kg",
"description": "Portland-Zement CEM I 32,5 R",
"unit": "Stk",
"priceNetCents": 890,
"purchasePriceCents": 450,
"vatRate": 19,
"minStock": 20,
"location": "Lager A, Regal 3",
"ean": "4012345678901",
"weightKg": 25.0,
"dimensions": { "lengthMm": 600, "widthMm": 300, "heightMm": 120 }
}
POST /api/v1/products/prod_01H/movements
Content-Type: application/json
{
"type": "in",
"quantity": 50,
"referenceType": "purchase_order",
"referenceId": "po_01H...",
"unitCostCents": 450,
"notes": "Wareneingang von Lieferant X"
}
Movement-Types:
- in — Eingang
- out — Ausgang (mit referenceType: 'order')
- transfer — zwischen Standorten
- adjust — Inventur-Korrektur (Pflicht: reason)
- loss — Verlust
GET /api/v1/products/prod_01H/stock
{
"productId": "prod_01H...",
"onHand": 47,
"reserved": 12,
"available": 35,
"minStock": 20,
"belowMinimum": false,
"lastMovementAt": "2026-04-23T..."
}
GET /api/v1/projects?status=active&embed=phases,tasks
POST /api/v1/projects
Content-Type: application/json
{
"name": "Neubau Produktionshalle",
"customerId": "cust_01H...",
"budget": {
"type": "time_and_material",
"hourlyRateCents": 9000,
"maxHours": 500,
"maxCents": 4500000
},
"plannedStart": "2026-05-01",
"plannedEnd": "2026-11-30",
"ownerId": "user_42"
}
POST /api/v1/projects/proj_01H/phases
Content-Type: application/json
{
"name": "Rohbau",
"plannedStart": "2026-05-15",
"plannedEnd": "2026-07-31",
"order": 2
}
POST /api/v1/projects/proj_01H/phases/phase_01H/tasks
Content-Type: application/json
{
"title": "Fundament ausheben",
"assigneeId": "user_43",
"estimatedHours": 40,
"dueDate": "2026-05-22"
}
POST /api/v1/time-entries
Content-Type: application/json
{
"projectId": "proj_01H...",
"phaseId": "phase_01H...",
"taskId": "task_01H...",
"userId": "user_42",
"date": "2026-04-24",
"durationMinutes": 480,
"description": "Bauablauf geplant, Lieferanten kontaktiert"
}
GET /api/v1/audit?from=2026-04-01&to=2026-04-24&actorType=ai&limit=100
GET /api/v1/audit/audit_01H8XABC
GET /api/v1/audit/verify?from=2026-01-01
Prüft die Integrität aller Einträge ab Datum. Response:
{
"verified": true,
"checkedCount": 12453,
"durationMs": 842,
"firstEntry": "audit_01G...",
"lastEntry": "audit_01H...",
"issues": []
}
Bei verified: false enthält issues die betroffenen IDs — sofort security@nemix.de kontaktieren.
POST /api/v1/audit/gdpdu-export
Content-Type: application/json
{
"from": "2025-01-01",
"to": "2025-12-31",
"scope": "finance"
}
Response: ZIP-Download-URL mit GDPdU-konformem Export.
GET /api/v1/analytics/kpis?period=current_month
{
"revenue": { "value": 145823, "currency": "EUR", "changePercent": 12.4 },
"openInvoices": { "count": 32, "totalCents": 5234500 },
"newCustomers": { "count": 14, "changePercent": -3.2 },
"lowStockItems": { "count": 8 }
}
GET /api/v1/analytics/timeseries?metric=revenue&groupBy=week&from=2026-01-01
{
"metric": "revenue",
"series": [
{ "period": "2026-W01", "value": 12453 },
{ "period": "2026-W02", "value": 14212 },
...
],
"unit": "cents"
}
POST /api/v1/analytics/reports/run
Content-Type: application/json
{
"reportId": "rpt_monthly_revenue",
"params": { "month": "2026-04" },
"format": "pdf"
}
Response: { "downloadUrl": "https://storage.nemix.../rpt_...", "expiresAt": "..." }
POST /api/v1/analytics/query
Content-Type: application/json
{
"entity": "invoice",
"select": ["status", "COUNT(*) as count", "SUM(amount_gross_cents) as total"],
"where": { "issueDate": { "gte": "2026-01-01" } },
"groupBy": ["status"]
}
Erlaubte Aggregations: COUNT, SUM, AVG, MIN, MAX. Joins nicht erlaubt — nutzen Sie stattdessen vordefinierte Reports.
Bei POST-Requests die kritisch sind (z. B. Payment):
POST /api/v1/invoices/inv_01H/payment
Idempotency-Key: unique-client-generated-key-123
Gleicher Key innerhalb 24 h → gleiche Response, keine Doppel-Buchung.
Retry-After-Header respektierenBei großen Listen nur nötige Felder abfragen:
GET /api/v1/customers?fields=id,name,email
Reduziert Response-Größe um bis zu 80 %.
GET /api/v1/orders?embed=customer,items.product
Tiefe Embeds sind teuer. Bei > 50 Orders ungünstig. Besser: separate Calls + Cache.
Kein Polling alle 30 Sekunden — stattdessen Outbound-Webhook subscriben:
POST /api/v1/webhooks/subscriptions
{ "url": "...", "events": ["invoice.paid"] }
Spart Ihnen Requests + ist in Echtzeit.
Massen-Operations über /bulk:
POST /api/v1/customers/bulk
Content-Type: application/json
{
"operation": "create",
"items": [
{ "name": "...", "email": "..." },
{ "name": "...", "email": "..." },
...
]
}
Bis 500 Items pro Call. Response enthält je Item Success/Failure.
/reports/*) — rückwärtskompatibel/graphql zusätzlich — rückwärtskompatibelGeplante offizielle SDK-Pakete:
bun add @nemix/sdk
# oder npm install @nemix/sdk
import { NemixClient } from '@nemix/sdk'
const client = new NemixClient({
apiKey: process.env.NEMIX_API_KEY!,
baseUrl: 'https://api.nemix.ainemix.de'
})
const customers = await client.customers.list({ limit: 50 })
const invoice = await client.invoices.create({ ... })
// Streaming AI Chat
for await (const chunk of client.ai.chat({ messages: [...] })) {
console.log(chunk)
}
// Typed Webhooks
client.on('invoice.paid', (event) => {
console.log('Invoice paid:', event.data.invoiceId)
})
pip install nemix-sdk
from nemix import NemixClient
client = NemixClient(api_key="nmx_live_...")
customers = client.customers.list(limit=50)
invoice = client.invoices.create(customer_id="...", items=[...])
# AI chat
for chunk in client.ai.chat(messages=[...]):
print(chunk)
go get github.com/nemix/nemix-go
Base-URL: https://sandbox.api.nemix.ainemix.de/api/v1
Ideal für Integration-Tests in CI/CD.
Entwickler können lokal einen Mock-Server laufen lassen:
docker run -p 3002:3001 nemix/api-mock:latest
Bietet alle Endpoints mit Fake-Daten, ohne DB/Redis/MinIO-Abhängigkeit.
Production-Traffic ausschließlich über HTTPS. HTTP-Requests werden abgewiesen (HSTS-Header gesetzt).
Jeder outbound Webhook ist HMAC-SHA256 signiert:
import hmac
import hashlib
def verify_signature(payload: bytes, signature_header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
Nur bei gültiger Signature verarbeiten.
API-Keys können auf IP-Bereiche begrenzt werden:
POST /api/v1/api-keys
{
"name": "Prod Integration Server",
"permissions": [...],
"allowedIps": ["52.10.0.0/16", "52.11.5.42"]
}
Requests von anderen IPs → HTTP 403.
Jeder API-Request landet im Audit-Log (siehe §21). Überwachen Sie das Log auf:
- Unerwartete IP-Adressen
- Ungewöhnliche Uhrzeiten
- Failed-Requests (Brute-Force?)
- Unerwartete Endpoints
Automatisch via Webhook-Subscription auf api_key.suspicious_activity.
# Läuft als Cron via n8n/eigenes Skript
from shopify import Client as ShopifyClient
from nemix import NemixClient
shopify = ShopifyClient(shop_url="mein-shop.myshopify.com", access_token="...")
nemix = NemixClient(api_key="...")
for sh_order in shopify.orders.list(status="paid", since="last_run"):
# Kunde finden oder anlegen
customer = nemix.customers.upsert(
externalId=f"shopify_{sh_order.customer.id}",
name=sh_order.customer.name,
email=sh_order.customer.email
)
# Auftrag anlegen
nemix.orders.create(
customerId=customer.id,
externalRef=f"shopify_{sh_order.id}",
items=[
{
"sku": item.sku,
"quantity": item.quantity,
"unitPriceNet": int(item.price * 100)
} for item in sh_order.line_items
],
status="confirmed"
)
Bank liefert MT940 oder CAMT.053 File. Parser:
import { parseCAMT053 } from '@nemix/banking-parsers'
import { NemixClient } from '@nemix/sdk'
const nemix = new NemixClient({ apiKey: process.env.NEMIX_KEY! })
const xml = await fs.readFile('kontoauszug.xml', 'utf-8')
const txs = parseCAMT053(xml)
for (const tx of txs.filter(t => t.creditDebit === 'credit')) {
// Suche offene Rechnungen des Kunden (Match per Verwendungszweck)
const match = await nemix.invoices.match({
amountCents: Math.round(tx.amount * 100),
reference: tx.remittanceInfo
})
if (match?.invoiceId) {
await nemix.invoices.recordPayment(match.invoiceId, {
amountCents: Math.round(tx.amount * 100),
date: tx.bookingDate,
method: 'bank_transfer',
reference: tx.remittanceInfo
})
}
}
n8n HTTP Request Node:
{
"method": "POST",
"url": "https://api.nemix.ainemix.de/api/v1/customers",
"headers": {
"Authorization": "Bearer {{$credentials.nemixApiKey}}",
"Content-Type": "application/json"
},
"body": {
"name": "={{$json.company_name}}",
"email": "={{$json.contact_email}}",
"tags": ["{{$json.source}}"]
}
}
Nemix kann als separate Credential in n8n registriert werden (API-Key-Typ).
Browser-Extension captured Daten aus Fremdsystem:
// content.js
const leadData = {
name: document.querySelector('.company-name').textContent,
email: document.querySelector('.contact-email').textContent,
phone: document.querySelector('.phone').textContent
}
await fetch('https://api.nemix.ainemix.de/api/v1/customers', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ ...leadData, tags: ['chrome-ext', 'lead'] })
})
Apps Script:
function syncToNemix() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
const rows = sheet.getDataRange().getValues().slice(1) // skip header
const apiKey = PropertiesService.getScriptProperties().getProperty('NEMIX_KEY')
for (const row of rows) {
const [name, email, vatId] = row
UrlFetchApp.fetch('https://api.nemix.ainemix.de/api/v1/customers', {
method: 'post',
headers: { 'Authorization': 'Bearer ' + apiKey },
payload: JSON.stringify({ name, email, vatId }),
contentType: 'application/json'
})
}
}
Kann ich die API ohne API-Key testen?
Nein, außer /health und /auth/login. Für schnelle Experimente: Sandbox-Environment + Demo-Key (bei onboarding@nemix.de erhältlich).
Wie teste ich meine Webhook-Integration lokal?
Nutzen Sie ngrok oder Cloudflare-Tunnel. Registrieren Sie die Tunnel-URL als Webhook-Subscription. Events kommen sofort an.
Kann ich mehrere Tenants mit einem API-Key verwalten?
Nein. Ein API-Key = ein Tenant. Bei Multi-Tenant-Tools (z. B. Reseller-Portal) müssen Sie pro Tenant einen eigenen Key speichern.
Wie reagiere ich auf Breaking-Changes?
Sie bekommen 8 Wochen Vorlauf per Mail an alle Key-Owner. Migration-Guides erscheinen in CHANGELOG.md.
Gibt es einen Bulk-Import für Custom-Integrationen?
Ja, via /bulk-Endpoints (siehe §23.6) oder direkt via CSV-Upload UI im Admin-Bereich.
Kann ich Webhooks filtern (nur bestimmte Events)?
Ja, über events-Array in der Subscription. Wildcard * möglich (z. B. invoice.* für alle Invoice-Events).
Werden Deletes Webhooks ausgelöst?
Ja, <entity>.deleted Event. Bei Soft-Delete kommt das Event auch bei restore als <entity>.restored.
Was passiert bei einem Schema-Change im Tenant?
Ihre API-Requests, die das neue Feld betreffen, müssen angepasst werden. Bestehende Felder bleiben stabil. Admin kann Schema-Änderungen per Webhook schema.updated broadcasten.
Dokument erzeugt am 2026-04-24 für Nemix ERP API v1.