Zum Inhalt

Umstieg von API v1 — API v2

Bestehende Integrationen sprechen die V1-REST-API: /api/inventory ohne Versionspräfix, drei X_REST_*-Zugangsdaten, Feldcodes wie I4201. API v2 liegt unter /api/v2, authentifiziert per Bearer-Token und antwortet im JSON:API-Format. Diese Seite listet, was ein V1-Client beim Umstieg anfassen muss — und was bewusst weiter funktioniert.

Die Kurzfassung: Feldnamen bleiben, V1-Codes sind weiterhin gültige Bezeichner. Schlüssel behalten ihren Wert, MTAG und CTAG heißen jetzt id (Details). URLs, Authentifizierung, Filter-Syntax und Antwort-Hülle ändern sich.

Endpunkte

V1 verwendet Singular-Pfade ohne Version, V2 Plural-Pfade unter /api/v2:

V1 V2
GET /api/inventory GET /api/v2/inventories
GET /api/calibration GET /api/v2/calibrations
GET /api/customer GET /api/v2/customers
GET /api/location GET /api/v2/locations
GET /api/repair GET /api/v2/repairs
GET /api/contact GET /api/v2/contacts
GET /api/standard GET /api/v2/calibrations/{id}/standards (Katalog: /api/v2/test-standards)
GET /api/result GET /api/v2/calibrations/{id}/results
POST /api/result/update (Bulk) PUT /api/v2/calibrations/{id}/results
GET/POST /api/report/{id} GET /api/v2/calibrations/{id}/reports, POST …/reports/generate
GET /api/document/{id} GET /api/v2/documents/{id} (Datei: …/download)
GET /api/user / group / role GET /api/v2/users / groups / roles
GET /api/app/version GET /api/v2/app/version
POST /api/auth/login POST /api/v2/auth/token (stellt Bearer-Token aus)
GET /api/{resource}/rules GET /api/v2/fields/{table}
GET /api/{resource}/get/{feld}/{wert} GET /api/v2/…?filter[feld]=wert
POST /api/{resource}/insert reguläres POST /api/v2/…
PUT /api/{resource}/{id} / POST /api/{resource}/update reguläres PUT /api/v2/…/{id}

IDs: MTAG, CTAG und die anderen Tags

In V1 hat jeder Datensatz einen Tag als Schlüssel: MTAG beim Gerät, CTAG bei der Kalibrierung, KTAG beim Kunden. In V2 heißt dieses Feld überall id. Der Wert ändert sich nicht, die Datenübernahme schreibt jeden Tag unverändert in die neue Spalte. Ein MTAG, den ein Altsystem gespeichert hat, zeigt in V2 auf dasselbe Gerät.

V1-Tag Datensatz V2
MTAG Gerät id von /api/v2/inventories
CTAG Kalibrierung id von /api/v2/calibrations
KTAG Kunde id von /api/v2/customers
LTAG Standort id von /api/v2/locations
RTAG Reparatur id von /api/v2/repairs

Zeigt ein Tag dagegen auf einen anderen Datensatz, ist er ein Fremdschlüssel und heißt in V2 nach seinem Ziel: der MTAG einer Kalibrierung wird zu inventory_id, der KTAG zu customer_id.

Merksatz für den ganzen Abschnitt:

Tag als eigener Schlüssel → id. Tag als Verweis auf etwas anderes → …_id.

In der Antwort steht die id neben type, eine Ebene über den Feldern:

{
  "data": {
    "id": "ea79f4c2-1b3d-4c88-9f21-7c0a5d13e001",
    "type": "inventory",
    "attributes": { "asset_number": "PM-100", "manufacturer": "FLUKE" }
  }
}

Ein Gerät über seinen MTAG holen

MTAG=ea79f4c2-1b3d-4c88-9f21-7c0a5d13e001

# über den Schlüssel
curl "https://…/api/inventory/$MTAG" -H "X-REST-USERNAME: …" …

# oder über den Getter
curl "https://…/api/inventory/get/MTAG/$MTAG" …
MTAG=ea79f4c2-1b3d-4c88-9f21-7c0a5d13e001

# derselbe Wert, jetzt als id im Pfad
curl "https://…/api/v2/inventories/$MTAG" -H "$AUTH"

# oder als Filter
curl "https://…/api/v2/inventories?filter[id]=$MTAG" -H "$AUTH"

Mehrere Geräte auf einmal

curl "https://…/api/inventory/get/MTAGs/$MTAG,bb12c7de-…" …
curl "https://…/api/v2/inventories?filter[id]=in:$MTAG,bb12c7de-…" -H "$AUTH"

Eine Kalibrierung über ihren CTAG holen

CTAG=3b17ad90-88c2-4e15-b0aa-2f9e6c4471aa

curl "https://…/api/calibration/$CTAG" …

# Messwerte gleich mit
curl "https://…/api/calibration/get/CTAG/$CTAG?getResult=1" …
CTAG=3b17ad90-88c2-4e15-b0aa-2f9e6c4471aa

curl "https://…/api/v2/calibrations/$CTAG" -H "$AUTH"

# Messwerte sind eine eigene Sub-Ressource
curl "https://…/api/v2/calibrations/$CTAG/results" -H "$AUTH"

Alle Kalibrierungen eines Geräts

curl "https://…/api/calibration/get/MTAG/$MTAG" …
# als Sub-Ressource des Geräts
curl "https://…/api/v2/inventories/$MTAG/calibrations" -H "$AUTH"

# oder als Filter auf den Fremdschlüssel
curl "https://…/api/v2/calibrations?filter[inventory_id]=$MTAG" -H "$AUTH"

# der V1-Code tut es hier auch: inventory_id ist ein Registry-Feld mit v1_name MTAG
curl "https://…/api/v2/calibrations?filter[MTAG]=$MTAG" -H "$AUTH"

Dasselbe Muster gilt für /api/v2/inventories/{mtag}/locations, /api/v2/inventories/{mtag}/repairs und /api/v2/customers/{ktag}/inventories.

Eine Stolperstelle

filter[MTAG] auf /api/v2/inventories liefert alle Geräte

Ein Filter auf ein Feld, das V2 an dieser Stelle nicht kennt, wird stillschweigend übersprungen. Die Antwort ist dann 200 mit dem kompletten Bestand, nicht 404 und nicht 422. Ein Client, der einfach data[0] nimmt, arbeitet ab da am falschen Datensatz weiter.

curl "https://…/api/v2/inventories?filter[MTAG]=$MTAG" -H "$AUTH"
# 200, "meta": { "total": 12480 }   ← alle Geräte, Filter verworfen

curl "https://…/api/v2/inventories?filter[id]=$MTAG" -H "$AUTH"
# 200, "meta": { "total": 1 }       ← richtig

Betroffen ist genau der Fall, in dem der Tag den eigenen Schlüssel meint: filter[MTAG] auf /inventories, filter[CTAG] auf /calibrations, filter[KTAG] auf /customers und so weiter. Als Fremdschlüssel (filter[MTAG] auf /calibrations) funktioniert derselbe Code problemlos.

Lösung: filter[id]=… oder gleich den Pfad /api/v2/inventories/{mtag}.

Gegenprobe für jeden Filter: meta.total mit und ohne Filter abrufen. Kommt zweimal dieselbe Zahl, wurde der Filter verworfen.

Spickzettel

Ziel V1 V2
Gerät per Schlüssel GET /api/inventory/{MTAG} GET /api/v2/inventories/{MTAG}
Gerät per Schlüssel, als Filter filter=[{"property":"MTAG","value":"…"}] filter[id]=…
Mehrere Geräte GET /api/inventory/get/MTAGs/a,b filter[id]=in:a,b
Kalibrierung per Schlüssel GET /api/calibration/{CTAG} GET /api/v2/calibrations/{CTAG}
Messwerte einer Kalibrierung GET /api/calibration/get/CTAG/{CTAG}?getResult=1 GET /api/v2/calibrations/{CTAG}/results
Kalibrierungen eines Geräts GET /api/calibration/get/MTAG/{MTAG} GET /api/v2/inventories/{MTAG}/calibrations
Kunde per Schlüssel GET /api/customer/{KTAG} GET /api/v2/customers/{KTAG}
Geräte eines Kunden filter=[{"property":"ktag","value":"…"}] GET /api/v2/customers/{KTAG}/inventories

Authentifizierung

V1 akzeptiert die drei Zugangsdaten als Header oder Query-Parameter. V2 stellt auf Bearer-Token um:

# Token ausstellen (einmalig bzw. bei Ablauf)
curl -X POST "https://…/api/v2/auth/token" \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "password": "…", "device_name": "sync-tool"}'

# Danach: Token im Authorization-Header
curl "https://…/api/v2/inventories" -H "Authorization: Bearer {token}"

Übergangsweise akzeptiert V2 die V1-Zugangsdaten weiter — als HTTP-Header, alle drei zusammen:

curl "https://…/api/v2/inventories" \
  -H "X-REST-USERNAME: user@example.com" \
  -H "X-REST-PASSWORD: …" \
  -H "X-REST-API-KEY: …"

Was nicht mehr funktioniert

Zugangsdaten als Query-Parameter (?HTTP_X_REST_USERNAME=…) und die Anmeldung über eine bestehende Browser-Session akzeptiert V2 nicht. Clients, die Credentials in der URL mitschicken, müssen auf Header oder Bearer-Token umstellen. Die X-REST-Header-Auth ist als veraltet markiert und für 7.0 zur Entfernung vorgesehen — neue Integrationen starten direkt mit Bearer-Token.

Antwortformat

V1 antwortet mit einer eigenen Hülle, V2 mit JSON:API:

{
  "success": true,
  "message": "Record(s) Found",
  "data": {
    "totalCount": 42,
    "inventory": [
      { "MTAG": "ea79…", "I4201": "PM-100", "I4202": "FLUKE" }
    ]
  }
}
{
  "data": [
    {
      "id": "ea79…",
      "type": "inventory",
      "attributes": { "asset_number": "PM-100", "manufacturer": "FLUKE" }
    }
  ],
  "meta": { "total": 42, "page": 1, "per_page": 25, "last_page": 2 }
}

Fehler kommen als RFC-7807-Problem-JSON (Content-Type: application/problem+json, Statuscode + detail) oder — bei fachlichen Konflikten — als {"error": {"status", "title", "detail"}}; ein success-Flag gibt es nicht mehr, maßgeblich ist der HTTP-Status.

Übergang: V1-Hülle anfordern

Mit Accept: application/vnd.calserver.v1+json formt V2 die Antwort-Hülle in das V1-Format um (success/message/totalCount). Das betrifft nur die Hülle, nicht die Feldnamen — und ist als veraltet markiert (Entfernung in 7.0). Für die Feldnamen siehe V1-Feldnamen.

Filtern und Sortieren

V1 nimmt Filter als JSON-Array, V2 als Query-Parameter pro Feld:

# Default-Operator ist LIKE (Teiltreffer)
curl "https://…/api/inventory?filter=[{\"property\":\"I4201\",\"value\":\"PM-1\"}]" …
curl "https://…/api/inventory?sort=[{\"property\":\"I4201\",\"direction\":\"DESC\"}]" …
# Default ist exakte Gleichheit — Teiltreffer explizit mit like:
curl "https://…/api/v2/inventories?filter[asset_number]=like:PM-1" -H "$AUTH"
curl "https://…/api/v2/inventories?sort=-asset_number" -H "$AUTH"

Migrationsfalle: LIKE vs. exakt

V1 filtert ohne operator-Angabe mit LIKE (Teiltreffer), V2 ohne Präfix exakt. Ein 1:1 übernommener Filter findet in V2 also weniger — Teiltreffer brauchen das like:-Präfix.

Operator-Präfixe in V2: like:, notlike:, contains:, ncontains:, startswith:, endswith:, ne:, gt:, gte:, lt:, lte:, in:a,b,c, between:a,b, empty, nempty. Mehrere Bedingungen pro Feld als Array (filter[feld][]=…&filter[feld][]=…, UND-verknüpft), filter_join[feld]=or schaltet auf ODER, filter_cs[feld]=1 erzwingt Groß-/Kleinschreibung. Sortierung: sort=feld aufsteigend, sort=-feld absteigend. Feldnamen dürfen dabei überall auch der V1-Code sein (filter[I4201]=…).

Paginierung

V1 V2
Parameter limit / offset page[size] / page[number]
Standard 100 Datensätze 25 Datensätze
Maximum limit=all (alles) page[size]=100
curl "https://…/api/v2/inventories?page[size]=100&page[number]=2" -H "$AUTH"

Die Gesamtzahl steht in meta.total (V1: data.totalCount). Ein limit=all-Äquivalent gibt es nicht — große Bestände seitenweise abrufen.

Feldnamen

Nichts zu tun: Die V1-Codes (I4201, C2301, K4601, …) bleiben in Filtern, Sortierung und Schreibzugriffen gültige Bezeichner, und auf migrierten Installationen sind sie sogar die Werksvorgabe des API-Namens. Antworten liefern die Codes auf Wunsch zusätzlich mit (?include_v1_names=1 oder Header X-CalServer-V1-Field-Names: 1). Details und Grenzfälle: V1-Feldnamen. Die vollständige Zuordnung einer Installation liefert GET /api/v2/fields/{table} (Attribute api_name, v1_name).