Mit der Data API verbinden
Die Data API stellt Business Entities als versionierte REST-Endpunkte unter
/api/data/v{version}/{route} bereit, zum Beispiel
/api/data/v1/customers. Ein Admin legt fest, welche Entities freigegeben sind,
welche Felder jede Version kennt und welche Rollen auflisten, lesen, anlegen, ändern oder
löschen dürfen. Ein Endpunkt kann auch ein Script Module aufrufen: Er nimmt Parameter
entgegen und antwortet mit dem, was das Modul zurückgibt. Diese Anleitung richtet sich an alle, die die Integration schreiben: wie du
dich verbindest, als Dienst mit einem API-Schlüssel oder als Benutzer mit Anmeldung, und wie
du die Endpunkte aufrufst.
Bevor du anfängst
Frag den Admin des Workspaces nach vier Dingen:
- Die API-Adresse. Derselbe Host, mit dem auch die App selbst spricht. Im Screen API Endpoints zeigt das Try-Panel sie in seiner curl-Zeile.
- Den Endpunkt und die Version, z. B.
customersVersion 1. Nur eine Version mit dem Status Active oder Deprecated antwortet, ein Draft nicht. - Eine auf diesem Endpunkt vergebene Rolle für die Operationen, die du brauchst, festgelegt im Who can call it-Grid des Endpunkts. Admin und Owner dürfen immer alles aufrufen.
- Einen Zugang mit dieser Rolle: einen API-Schlüssel für einen Dienst oder ein Benutzerkonto für eine Person oder ein schnelles Skript.
Läuft etwas unbeaufsichtigt, etwa eine nächtliche Synchronisation, ein Webshop oder ein Reporting-Tool, nimm einen API-Schlüssel. Er braucht kein Passwort, keine Anmeldung und keinen Refresh, und er öffnet die Data API und sonst nichts.
Einen Dienst mit einem API-Schlüssel verbinden
Ein Admin legt einen API client im Screen API Clients an: mit einem Namen, seinen Rollen und optional einem Ablaufdatum. Admin und Owner lassen sich einem Client nicht zuweisen, er kann auf jedem Endpunkt also nur das, was seinen Rollen dort gewährt ist. Der Schlüssel wird genau einmal angezeigt, beim Anlegen des Clients. Gespeichert wird nur ein Fingerabdruck davon, sodass niemand ihn später nachschlagen kann, nicht einmal ein Admin.
Schicke den Schlüssel bei jedem Aufruf als Bearer Token mit:
API=https://api.example.com
KEY=ask_api_acme.q7Vt0r2Lk_dP9sXw3eNfH1mA6yZc4uBgJ8oTiR5vWnE
curl "$API/api/data/v1/customers" -H "Authorization: Bearer $KEY" -
Der Schlüssel funktioniert nur unter
/api/data. Überall sonst im Produkt wird er abgelehnt, ein geleakter Schlüssel kommt also weder an die übrigen Daten der App noch an ihre Einstellungen heran. -
Er bleibt gültig, bis er abläuft, der Client deaktiviert oder gelöscht wird oder ein Admin
ihn rotiert, also durch einen neuen ersetzt. Das wirkt jeweils ab dem nächsten
Aufruf, der dann mit
401antwortet. - Änderungen, die der Dienst vornimmt, tragen den Namen des Clients als Autor, und Business Events sehen ihn als Benutzer.
-
Der Schlüssel nennt seinen Workspace (
ask_api_{workspace}.{secret}), eine Auswahl des Workspaces ist also nicht nötig. Bewahre ihn in einem Secret Store auf, niemals im Code.
Weiter geht es mit Einen Endpunkt aufrufen.
Als Benutzer verbinden: Anmelden
Nutze ein eigenes Konto, nicht das einer Person. Ein Token aus einer Anmeldung kann alles, was dieser Benutzer in der App kann, nicht nur die Data API aufrufen. Gib dem Konto deshalb die vergebene Rolle und sonst nichts, und bewahre sein Passwort in einem Secret Store auf. Für einen Dienst ist ein API-Schlüssel die bessere Wahl.
Sende E-Mail-Adresse und Passwort des Kontos per POST an /api/v1/auth/login:
API=https://api.example.com
curl -X POST "$API/api/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{"email": "webshop-sync@example.com", "password": "..."}' Die Antwort enthält die Tokens:
{
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
"refreshToken": "q8Zr0dW3...",
"idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
"expiresIn": 900,
"redirectHost": null
} -
tokenist das Access Token. Schicke es bei jedem Aufruf mit. Es läuft nachexpiresInSekunden ab, standardmäßig nach 15 Minuten. -
refreshTokenholt dir ein neues Paar ohne Passwort (siehe Eine Benutzer-Session am Leben halten). Es gilt standardmäßig 30 Tage und funktioniert genau einmal. idTokenundredirectHostsind für die Browser-App gedacht, ignoriere sie.
Eine abgelehnte Anmeldung antwortet mit 401 und einem code:
invalid_credentials, disabled (das Konto ist deaktiviert) oder
no_tenant (das Konto gehört zu keinem aktiven Workspace). Zu viele Fehlversuche
führen zu 429 mit locked_out. Außerdem ist die Anmeldung auf 20 Versuche
pro Minute und IP-Adresse begrenzt. Melde dich also einmal an und behalte die Session, statt dich
für jeden Aufruf neu anzumelden.
Einen Endpunkt aufrufen
Setze den API-Schlüssel oder das Access Token in den Authorization-Header. Unten
steht $TOKEN für beides. Ein List-Aufruf liefert eine Seite mit Einträgen und die
Gesamtzahl:
curl "$API/api/data/v1/customers?pageSize=2&sort=name" \
-H "Authorization: Bearer $TOKEN" {
"items": [
{ "id": 1, "name": "Acme", "email": "info@acme.test", "creditLimit": 500 },
{ "id": 2, "name": "Globex", "email": "hello@globex.test", "creditLimit": 50 }
],
"page": 1,
"pageSize": 2,
"totalCount": 3
} Die Feldnamen sind die, die der Admin dieser Version gegeben hat, und müssen nicht mit den Spalten in der Datenbank übereinstimmen. Einen einzelnen Eintrag lesen, anlegen, ändern und löschen sieht so aus:
# One item
curl "$API/api/data/v1/customers/1" -H "Authorization: Bearer $TOKEN"
# Create: answers 201 with the stored item and a Location header
curl -X POST "$API/api/data/v1/customers" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Hooli", "email": "hi@hooli.test"}'
# Change some fields
curl -X PATCH "$API/api/data/v1/customers/4" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"email": "sales@hooli.test"}'
# Delete: answers 204
curl -X DELETE "$API/api/data/v1/customers/4" -H "Authorization: Bearer $TOKEN" - PATCH ändert nur die Felder im Body. PUT macht dasselbe, lehnt aber einen Body ab, in dem ein Pflichtfeld fehlt.
-
Die ID kommt immer aus der URL. Eine
idim Body eines Create wird ignoriert, ein Create überschreibt also nie einen bestehenden Eintrag. -
Schreibgeschützte Felder im Body werden ignoriert, du kannst einen Eintrag also genau so
zurückschicken, wie du ihn abgerufen hast. Ein Feld, das die Version nicht kennt, wird mit
400abgelehnt, das fängt Tippfehler ab. - Schreibzugriffe verhalten sich genau wie Speichern in der App: Business Events werden ausgelöst, die Validierung läuft, und geöffnete Ansichten aktualisieren sich.
Ein Script Module aufrufen
Manche Endpunkte führen ein Script Module aus, statt die Zeilen einer Entity auszuliefern, für Arbeit, die mehr ist als Lesen oder Schreiben: den Preis eines Auftrags berechnen, einen Termin buchen, einen Bericht erstellen. Jede Version eines solchen Endpunkts antwortet auf genau eine Methode, und das OpenAPI-Dokument sagt, auf welche: POST nimmt die Parameter als JSON-Objekt im Body, GET im Query-String.
# A version called with POST: the parameters as a JSON object
curl -X POST "$API/api/data/v1/quote" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"quantity": 3, "express": true}'
# A version called with GET: the parameters in the query string
curl "$API/api/data/v1/planner-week?weekStart=2026-09-21" \
-H "Authorization: Bearer $TOKEN" Die Antwort ist das, was das Modul zurückgibt, als JSON:
{
"quantity": 3,
"total": 172.5,
"deliveryDate": "2026-09-28"
} -
Die Parameternamen sind die, die der Admin der Version gegeben hat. Jeder Wert wird vor dem
Lauf des Moduls gegen seinen Typ geprüft: Ganzzahl, Zahl, true oder false, ein Datum mit oder
ohne Uhrzeit (
2026-09-25,2026-09-25T14:30:00Z) oder Text. Ein Wert des falschen Typs, ein Parameter, den die Version nicht kennt, und ein fehlender Pflichtparameter ergeben400;nullzählt als weggelassen. -
Ein Modul, das nichts zurückgibt, antwortet mit
204 No Content. Eines, das fehlschlägt, antwortet mit500und der eigenen Meldung des Moduls indetail. - Der Aufruf braucht eine Rolle, der auf dem Endpunkt die Operation Call gewährt wurde. Ein Modul, das der Admin als admin only markiert hat, antwortet nur Admin und Owner, ein API-Schlüssel erreicht es also nie.
- Das Modul läuft echt, genau wie wenn die App es ausführt: Was es schreibt, löst Business Events aus, und Mails, die es verschickt, gehen wirklich raus.
-
Ein Aufruf mit der anderen Methode oder auf einem Item-Pfad wie
/quote/1ergibt405, und derAllow-Header nennt die richtige Methode.
Eine Benutzer-Session am Leben halten
Das gilt nur für einen angemeldeten Benutzer, ein API-Schlüssel braucht nichts davon. Bevor
das Access Token abläuft, oder wenn ein Aufruf mit 401 antwortet, tauschst du das
Refresh Token gegen ein neues Paar:
curl -X POST "$API/api/v1/auth/refresh" \
-H "Content-Type: application/json" \
-d '{"refreshToken": "q8Zr0dW3..."}' Die Antwort hat dieselbe Form wie bei der Anmeldung, mit einem neuen Refresh Token. Speichere es und wirf das alte weg: Ein Refresh Token funktioniert nur einmal. Wird ein bereits verbrauchtes nach Ablauf einer Minute erneut vorgelegt, gilt das als Diebstahl und beendet die gesamte Session, danach musst du dich neu anmelden. Gib deshalb jedem Prozess oder Worker eine eigene Session und teile nie ein Refresh Token zwischen ihnen.
Ein Refresh antwortet mit 401, wenn die Session beendet ist: Sie ist abgelaufen,
das Konto wurde deaktiviert oder aus dem Workspace entfernt, oder die Session wurde per Logout
beendet. Melde dich in diesem Fall neu an. Rollenänderungen landen beim nächsten Refresh im
Token, eine entzogene Rolle wirkt also spätestens nach 15 Minuten nicht mehr. So beendest du
eine Session selbst:
curl -X POST "$API/api/v1/auth/logout" \
-H "Content-Type: application/json" \
-d '{"refreshToken": "q8Zr0dW3..."}' Ein Konto in mehr als einem Workspace
Die Anmeldung wählt einen Workspace für die Session: den, zu dem die aufgerufene Adresse
gehört, sofern das Konto dort Mitglied ist, andernfalls den zuletzt genutzten. Deshalb sollte
ein eigenes Integrationskonto nur zu einem Workspace gehören. Gehört es zu mehreren, liste sie
auf und wechsle mit der tenantId des gewünschten:
# The workspaces this account belongs to
curl "$API/api/v1/auth/tenants" -H "Authorization: Bearer $TOKEN"
# A new token pair for one of them
curl -X POST "$API/api/v1/auth/switch-tenant" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"tenantId": "3f1c2a9e-..."}' Der Wechsel liefert ein neues Token-Paar für diesen Workspace, mach damit weiter wie nach einer Anmeldung.
Filtern, Sortieren und Paging
# Customers whose name contains "acme", highest credit limit first
curl -G "$API/api/data/v1/customers" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "filter[name][like]=acme" \
--data-urlencode "filter[creditLimit][gte]=100" \
--data-urlencode "sort=-creditLimit" \
--data-urlencode "page=2" \
--data-urlencode "pageSize=50" | Parameter | Bedeutung |
|---|---|
page | Seitennummer, beginnend bei 1. |
pageSize | Einträge pro Seite, standardmäßig 50. Jede Version legt ein Maximum fest (100, sofern der Admin es nicht geändert hat), mehr anzufordern ergibt einen 400. |
sort | Ein Feld, mit vorangestelltem - für absteigende Reihenfolge. Ohne Angabe die neuesten zuerst. |
search | Text, der mit jedem Feld abgeglichen wird, das die Version zurückgibt. |
filter[field] | Gleich dem Wert. |
filter[field][op] | Vergleich mit einem Operator, siehe unten. Bei mehreren Filtern müssen alle zutreffen. |
| Operator | Trifft zu auf |
|---|---|
eq, ne | Gleich, ungleich. |
gt, gte, lt, lte | Größer oder kleiner als. Zahlen, Datumswerte und Text. |
like, startswith, endswith | Text, der den Wert enthält, damit beginnt oder damit endet, ohne Beachtung der Groß- und Kleinschreibung. Nur Textfelder. |
in | Einer der Werte aus einer kommagetrennten Liste: filter[id][in]=1,2,3. |
null | true für leere Werte, false für gefüllte. |
Werte werden gegen den Typ des Felds geprüft, filter[creditLimit]=lots ergibt also
einen 400, der das Feld nennt. Filtern, sortieren und durchsuchen lassen sich nur
Felder, die die Version zurückgibt, und ein unbekannter Parameter wird abgelehnt statt ignoriert,
sodass ein Tippfehler nie stillschweigend alles zurückliefert. Parameter, die mit _
beginnen, werden ignoriert, damit du sie zum Cache-Busting nutzen kannst.
Versionen
Die Version ist Teil des Pfads, und mehrere Versionen eines Endpunkts können nebeneinander live sein. Eine veröffentlichte Version ändert sich nie so, dass deine Integration bricht: Felder werden nie entfernt oder umbenannt, und nichts wird nachträglich zur Pflicht. Eine Version, die ein Script Module aufruft, behält ebenso ihre Methode und ihre Parameter und bekommt nur optionale hinzu. Solche Änderungen kommen als neue Version.
- Active- und Deprecated-Versionen antworten.
- Eine Version, die nicht existiert oder noch ein Draft ist, antwortet mit
404und listet die vorhandenen Versionen auf. - Eine Retired-Version antwortet mit
410 Goneund nennt die Version, auf die du wechseln solltest.
Jede Antwort sagt, welche Versionen es gibt. Eine Deprecated-Version sagt das zusätzlich über sich selbst, und wann sie wegfällt:
HTTP/1.1 200 OK
api-supported-versions: 1, 2
api-deprecated-versions: 1
Deprecation: @1788220800
Sunset: Thu, 31 Dec 2026 00:00:00 GMT
Link: <https://api.example.com/api/data/v2/customers>; rel="successor-version" Deprecation ist der Zeitpunkt, zu dem sie auf Deprecated gesetzt wurde (eine
Unix-Zeit), Sunset das Datum, ab dem sie nicht mehr antwortet, sofern der Admin
eines gesetzt hat, und Link verweist auf die nächste Version. Bei jedem
Deprecation-Header eine Warnung zu loggen ist der einfachste Weg, rechtzeitig
davon zu erfahren.
Fehler
Eine Ablehnung durch die Data API ist ein standardkonformes Problem-Dokument (RFC 7807), dessen
detail beschreibt, was falsch war:
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
{
"title": "Forbidden",
"status": 403,
"detail": "None of your roles may Delete on 'customers'."
}
Eine Ausnahme: Wenn die Validierung eines Business Events einen Schreibzugriff ablehnt, ist der
422-Body { "message": "...", "errorColumns": [...] } und
nennt die betroffenen Felder mit den Spaltennamen der Datenbank selbst.
| Status | Bedeutung |
|---|---|
400 | Die Anfrage ist fehlerhaft: ein unbekanntes Feld oder ein unbekannter Parameter, ein Wert vom falschen Typ, ein fehlendes Pflichtfeld. |
401 | Token oder Schlüssel fehlt oder ist nicht mehr gültig. Ein Benutzer führt einen Refresh aus und versucht es erneut. Ein API-Schlüssel ist abgelaufen oder wurde rotiert, oder sein Client wurde deaktiviert oder gelöscht. |
403 | Keine der Rollen des Kontos darf diese Operation auf diesem Endpunkt ausführen. |
404 | Endpunkt, Version oder Eintrag existiert nicht. |
405 | Diese Version bietet die Operation nicht an, z. B. Löschen, oder ein Endpunkt eines Script Modules wurde mit der anderen Methode aufgerufen. |
409 | Die Daten stehen im Konflikt: Ein eindeutiger Wert existiert bereits, oder andere Datensätze verweisen noch auf diesen. |
410 | Die Version wurde außer Betrieb genommen (Retired). |
422 | Eine Geschäftsregel oder eine Tabellenregel hat die Werte abgelehnt. |
429 | Zu viele Anfragen. Warte kurz und versuche es erneut. |
500 | Das Script Module hinter einem Endpunkt ist fehlgeschlagen; detail enthält seine Meldung. |
OpenAPI-Beschreibung
GET /api/data/openapi.json, mit deinem Token aufgerufen, liefert ein
OpenAPI-3-Dokument mit genau den Versionen und Operationen, die dein Konto nutzen darf,
samt jedem Feld und seinem Typ. Importiere es in Postman oder Insomnia, oder generiere daraus
einen typisierten Client. Rufe es erneut ab, nachdem der Admin eine neue Version veröffentlicht hat.
Beispiele in Python
Mit einem API-Schlüssel genügt der Header:
import os
import requests
API = os.environ["ARCHESTACK_API"]
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['ARCHESTACK_API_KEY']}"
r = session.get(f"{API}/api/data/v1/customers", params={"pageSize": 100}, timeout=30)
r.raise_for_status()
for customer in r.json()["items"]:
print(customer["id"], customer["name"])
Meldest du dich als Benutzer an, musst du dich um die Session kümmern. Dieses Beispiel meldet
sich an, führt eine Minute vor Ablauf des Tokens einen Refresh aus, fängt einen
401 ab, liest alle Seiten und legt einen Eintrag an. Hinterlege die Adresse und
die Zugangsdaten in Umgebungsvariablen.
import os
import time
import requests
API = os.environ["ARCHESTACK_API"] # e.g. https://api.example.com
EMAIL = os.environ["ARCHESTACK_EMAIL"]
PASSWORD = os.environ["ARCHESTACK_PASSWORD"]
class DataApi:
"""One signed-in session. Give every process or worker its own."""
def __init__(self):
self.token = None
self.refresh_token = None
self.expires_at = 0.0
def _keep(self, body):
self.token = body["token"]
self.refresh_token = body["refreshToken"] # always replace it: each one works once
self.expires_at = time.time() + body["expiresIn"] - 60
def sign_in(self):
r = requests.post(f"{API}/api/v1/auth/login",
json={"email": EMAIL, "password": PASSWORD}, timeout=30)
r.raise_for_status()
self._keep(r.json())
def refresh(self):
r = requests.post(f"{API}/api/v1/auth/refresh",
json={"refreshToken": self.refresh_token}, timeout=30)
if r.status_code == 401: # the session ended: start a new one
return self.sign_in()
r.raise_for_status()
self._keep(r.json())
def call(self, method, path, **kwargs):
if self.token is None:
self.sign_in()
elif time.time() >= self.expires_at:
self.refresh()
r = self._send(method, path, **kwargs)
if r.status_code == 401: # expired early or revoked: refresh once and retry
self.refresh()
r = self._send(method, path, **kwargs)
return r
def _send(self, method, path, **kwargs):
headers = {"Authorization": f"Bearer {self.token}"}
return requests.request(method, f"{API}/api/data{path}",
headers=headers, timeout=30, **kwargs)
api = DataApi()
# Every page of customers
page = 1
while True:
r = api.call("GET", "/v1/customers", params={"page": page, "pageSize": 100})
r.raise_for_status()
body = r.json()
for customer in body["items"]:
print(customer["id"], customer["name"])
if page * body["pageSize"] >= body["totalCount"]:
break
page += 1
# Create one
r = api.call("POST", "/v1/customers", json={"name": "Hooli", "email": "hi@hooli.test"})
if r.status_code == 201:
print("created", r.json()["id"])
else:
print("refused:", r.json().get("detail")) Trial-Umgebungen: Dort können nur das Owner-Konto des Trials und die vom Owner angelegten API clients die API aufrufen.
Wie es weitergeht
- Business Entities - woraus ein Endpunkt liest und warum eine Entity, die eine One-to-Many-Tabelle joint, einen Eintrag pro gejointer Zeile zurückgibt.
- User Management - lege das Integrationskonto und seine Rolle an.