Nemix ERP — API-Referenz

REST-API für Entwickler. Authentication, Endpoints, Webhooks, Universal-REST-Connector, Beispiele.
Version 0.5.0 · API v1 · Stand: 2026-04-24


Inhaltsverzeichnis

  1. Basics
  2. Authentication
  3. Tenant-Header
  4. Rate-Limits
  5. Pagination
  6. Filtering & Sorting
  7. Endpoint-Übersicht
  8. Customers
  9. Orders
  10. Workflows
  11. AI
  12. Integrations
  13. Webhooks (Inbound)
  14. Webhooks (Outbound)
  15. Universal REST Connector
  16. Error-Codes
  17. SDK-Beispiele

1. Basics

1.1 Base-URL

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

1.2 OpenAPI-Spec

Live-Swagger-UI: https://api.nemix.ainemix.de/docs
Raw-JSON: https://api.nemix.ainemix.de/openapi.json

1.3 Format

1.4 Versionierung

Pfad-Versionierung: /api/v1/.... Breaking Changes ergeben /api/v2/.... Vorgängerversion läuft mindestens 12 Monate weiter, mit Sunset-Header und Doku.


2. Authentication

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).

2.2 API-Key (Server-to-Server)

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).

2.4 Logout

POST /api/v1/auth/logout

Response 204.


3. Tenant-Header

Multi-Tenant: jeder Request muss den Tenant identifizieren. Drei Wege (in Reihenfolge geprüft):

  1. Subdomain: https://mueller-bau.nemix.ainemix.de/... → Slug = mueller-bau
  2. Header: X-Tenant-ID: tenant_demo (für Multi-Tenant-Tools)
  3. API-Key: ist tenant-gebunden, daraus abgeleitet

Bei Konflikt: API-Key gewinnt > Header gewinnt > Subdomain.


4. Rate-Limits

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

5. Pagination

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.


6. Filtering & Sorting

6.1 Einfache Filter

GET /api/v1/customers?status=active&country=DE

6.2 Operator-Filter

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.

6.3 Volltext-Suche

GET /api/v1/customers?q=müller

Nutzt Meilisearch im Hintergrund — typo-tolerant.

6.4 Sortierung

GET /api/v1/customers?sort=createdAt:desc,name:asc

6.5 Field-Selection

GET /api/v1/customers?fields=id,name,email

Reduziert Payload-Größe.

6.6 Embed-Relations

GET /api/v1/orders?embed=customer,items

Statt N+1 Calls vom Client.


7. Endpoint-Übersicht

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

8. Customers

8.1 Liste

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
}

8.2 Einzeln

GET /api/v1/customers/cust_01H8XABC

8.3 Anlegen

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.

8.4 Aktualisieren

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.

8.5 Löschen (Soft-Delete)

DELETE /api/v1/customers/cust_01H8XABC

Response 204. Datensatz hat danach deletedAt gesetzt, ist über ?includeDeleted=true weiter abrufbar (nur Admin).


9. Orders

9.1 Liste mit Filter

GET /api/v1/orders?status=in_progress&customerId=cust_01H8XABC&embed=items

9.2 Anlegen

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."
}

9.3 Status ändern

POST /api/v1/orders/order_01H8/transition
Content-Type: application/json

{ "to": "confirmed" }

Server prüft erlaubte Transitions. Bei Verstoß HTTP 422.

9.4 Rechnung erzeugen

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.


10. Workflows

10.1 Liste

GET /api/v1/workflows

10.2 Anlegen

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
}

10.3 Run-History

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" } }
  ]
}

10.4 Manuell triggern

POST /api/v1/workflows/wf_01H8/trigger
Content-Type: application/json

{ "context": { "customerId": "cust_01H8XABC" } }

11. AI

11.1 Chat (Streaming)

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}}

11.2 AI-Aktion direkt ausführen (ohne Chat)

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.

11.3 Token-Verbrauch abfragen

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 }
}

12. Integrations

12.1 Liste

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" }
  ]
}

12.2 Verbinden (OAuth-Flow)

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.

12.3 Sync triggern

POST /api/v1/integrations/int_shopify_01/sync
Content-Type: application/json

{ "entities": ["products", "orders"] }

12.4 Trennen

DELETE /api/v1/integrations/int_shopify_01

13. Webhooks (Inbound)

Externe Systeme schicken Events an Nemix.

13.1 Eingehender Webhook (generisch)

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.

13.2 Webhook anlegen

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": "..."
}

14. Webhooks (Outbound)

Nemix schickt Events an Ihre Endpoints.

14.1 Subscription anlegen

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
}

14.2 Event-Format

{
  "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

14.3 Retry-Strategie

Nicht-2xx-Response → Retry mit Exponential-Backoff: 30s, 5min, 30min, 2h, 6h, 24h. Nach 6 Fehlversuchen wird Subscription auf failing gesetzt.

14.4 Verfügbare Events

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

15. Universal REST Connector

Bindet jede REST-API ohne Code an. Endpoints werden Tools im Workflow-Builder.

15.1 Connector anlegen

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" }
        }
      }
    }
  ]
}

15.2 Mit OpenAPI-Spec

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.

15.3 Endpoint aufrufen

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.

15.4 Auth-Typen

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).


16. Error-Codes

16.1 HTTP-Status

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)

16.2 Error-Body

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "email is required",
    "details": [
      { "field": "email", "issue": "missing" }
    ],
    "requestId": "req_01H..."
  }
}

16.3 Application-Error-Codes

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

17. SDK-Beispiele

17.1 cURL

# 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"}'

17.2 JavaScript (fetch)

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))
  }
}

17.3 Python (requests)

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"]

17.4 Universal REST Connector — Custom Build

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.

Änderungen an dieser Spec

Versionshistorie: CHANGELOG.md. Breaking Changes werden mindestens 8 Wochen vorher per Mail an alle API-Key-Owner kommuniziert.

Status & Health


18. Invoices

18.1 Liste

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
    }
  ]
}

18.2 Rechnung anlegen (direkt, ohne Auftrag)

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
}

18.3 Versenden

POST /api/v1/invoices/inv_01H8XABC/send
Content-Type: application/json

{
  "via": "peppol",
  "recipient": null
}

18.4 Zahlung buchen

POST /api/v1/invoices/inv_01H8XABC/payment
Content-Type: application/json

{
  "amountCents": 119000,
  "date": "2026-04-22",
  "method": "bank_transfer",
  "reference": "Buchungstext aus Bank"
}

18.5 Storno

POST /api/v1/invoices/inv_01H8XABC/cancel
Content-Type: application/json

{ "reason": "Fehlerhafte Position", "issueCorrective": true }

Erzeugt Storno-Rechnung + Korrektur-Rechnung, GoBD-konform.

18.6 PDF herunterladen

GET /api/v1/invoices/inv_01H8XABC/pdf
Accept: application/pdf

Zusätzlich:
- ?format=xrechnung — nur XML
- ?format=zugferd — PDF mit eingebettetem XML


19. Products (Lager)

19.1 Liste mit Filter

GET /api/v1/products?category=material&lowStock=true&limit=100

19.2 Anlegen

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 }
}

19.3 Bewegung erfassen

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

19.4 Bestandsübersicht

GET /api/v1/products/prod_01H/stock
{
  "productId": "prod_01H...",
  "onHand": 47,
  "reserved": 12,
  "available": 35,
  "minStock": 20,
  "belowMinimum": false,
  "lastMovementAt": "2026-04-23T..."
}

20. Projects

20.1 Liste

GET /api/v1/projects?status=active&embed=phases,tasks

20.2 Anlegen

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"
}

20.3 Phase anlegen

POST /api/v1/projects/proj_01H/phases
Content-Type: application/json

{
  "name": "Rohbau",
  "plannedStart": "2026-05-15",
  "plannedEnd": "2026-07-31",
  "order": 2
}

20.4 Task

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"
}

20.5 Zeitbuchung

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"
}

21. Audit-Log

21.1 Einträge abfragen

GET /api/v1/audit?from=2026-04-01&to=2026-04-24&actorType=ai&limit=100

21.2 Einzel-Eintrag

GET /api/v1/audit/audit_01H8XABC

21.3 Hash-Chain prüfen

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.

21.4 GDPdU-Export für Betriebsprüfer

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.


22. Analytics

22.1 KPI-Werte

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 }
}

22.2 Zeitreihe

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"
}

22.3 Report ausführen

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": "..." }

22.4 Custom-Query (nur Manager+)

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.


23. Best-Practices

23.1 Idempotenz

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.

23.2 Retries

23.3 Field-Selection nutzen

Bei großen Listen nur nötige Felder abfragen:

GET /api/v1/customers?fields=id,name,email

Reduziert Response-Größe um bis zu 80 %.

23.4 Embed sparsam

GET /api/v1/orders?embed=customer,items.product

Tiefe Embeds sind teuer. Bei > 50 Orders ungünstig. Besser: separate Calls + Cache.

23.5 Webhooks statt Polling

Kein Polling alle 30 Sekunden — stattdessen Outbound-Webhook subscriben:

POST /api/v1/webhooks/subscriptions
{ "url": "...", "events": ["invoice.paid"] }

Spart Ihnen Requests + ist in Echtzeit.

23.6 Bulk-Endpoints

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.


24. Changelog der API

v1 (current, stable seit 0.5.0)

Vorherige Versionen

Geplante Änderungen


25. SDK-Paket (Release Q2 2026)

Geplante offizielle SDK-Pakete:

25.1 TypeScript/JavaScript

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)
})

25.2 Python

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)

25.3 Go (geplant Q3 2026)

go get github.com/nemix/nemix-go

26. Offline / Sandbox

26.1 Sandbox-Environment

Base-URL:  https://sandbox.api.nemix.ainemix.de/api/v1

Ideal für Integration-Tests in CI/CD.

26.2 Mock-Endpoint lokal

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.


27. Security-Best-Practices für API-Nutzung

27.1 Key-Management

27.2 HTTPS Pflicht

Production-Traffic ausschließlich über HTTPS. HTTP-Requests werden abgewiesen (HSTS-Header gesetzt).

27.3 Webhook-Signatures verifizieren

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.

27.4 IP-Whitelisting

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.

27.5 Audit-Einsicht

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.


28. Integration-Cookbook

28.1 Shopify → Nemix Bestellungen importieren

# 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"
    )

28.2 Nemix → Bank (Zahlungseingang)

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
    })
  }
}

28.3 n8n Workflow → Nemix

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).

28.4 Chrome-Extension Lead-Erfassung

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'] })
})

28.5 Google Sheets → Nemix Sync

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'
    })
  }
}

29. FAQ für Entwickler

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.