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¶
Mehrere Geräte auf einmal¶
Eine Kalibrierung über ihren CTAG holen¶
Alle Kalibrierungen eines Geräts¶
# 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:
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:
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 |
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).