Verbinden met de Data API
De Data API stelt business entities beschikbaar als geversioneerde REST-endpoints onder
/api/data/v{version}/{route}, bijvoorbeeld
/api/data/v1/customers. Een admin bepaalt welke entities beschikbaar zijn, welke
velden elke versie kent, en welke rollen de lijst mogen opvragen en items mogen lezen,
aanmaken, bijwerken of verwijderen. Een endpoint kan ook een script module aanroepen: het
neemt parameters aan en antwoordt met wat de module teruggeeft. Deze handleiding is voor wie de integratie schrijft: hoe je
verbinding maakt, als service met een API-sleutel of als gebruiker die inlogt, en hoe je de
endpoints aanroept.
Voordat je begint
Vraag de admin van de werkruimte om vier dingen:
- Het API-adres. Dezelfde host waarmee de app zelf praat. In het scherm API Endpoints toont het Try-paneel het in zijn curl-regel.
- Het endpoint en de versie, bv.
customersversie 1. Alleen een versie met de status Active of Deprecated geeft antwoord, een Draft niet. - Een rol met rechten op dat endpoint voor de bewerkingen die je nodig hebt, ingesteld in het Who can call it-raster van het endpoint. Admin en owner kunnen altijd alles aanroepen.
- Toegang met die rol: een API-sleutel voor een service, of een gebruikersaccount voor een persoon of een snel script.
Gebruik voor alles wat zonder toezicht draait, zoals een nachtelijke synchronisatie, een webshop of een rapportagetool, een API-sleutel. Die heeft geen wachtwoord, geen login en geen refresh nodig, en geeft toegang tot de Data API en tot niets anders.
Een service verbinden met een API-sleutel
Een admin maakt een API client aan in het scherm API Clients: een naam, de rollen die de client krijgt, en optioneel een vervaldatum. Admin en owner kunnen niet aan een client worden toegekend, dus een client bereikt alleen wat zijn rollen op elk endpoint mogen. De sleutel wordt precies één keer getoond, bij het aanmaken van de client. Alleen een vingerafdruk ervan wordt opgeslagen, dus niemand kan hem later nog opzoeken, ook een admin niet.
Stuur de sleutel bij elke aanroep mee als bearer token:
API=https://api.example.com
KEY=ask_api_acme.q7Vt0r2Lk_dP9sXw3eNfH1mA6yZc4uBgJ8oTiR5vWnE
curl "$API/api/data/v1/customers" -H "Authorization: Bearer $KEY" -
De sleutel werkt alleen onder
/api/data. Overal elders in het product wordt hij geweigerd, dus met een uitgelekte sleutel kom je niet bij de andere data of de instellingen van de app. -
Hij blijft geldig tot hij verloopt, de client wordt uitgeschakeld of verwijderd, of een admin
hem roteert en zo door een nieuwe vervangt. Elk daarvan geldt vanaf de volgende
aanroep, die dan
401antwoordt. - Wijzigingen die de service maakt, krijgen de naam van de client als auteur, en business events zien de client als de gebruiker.
-
De sleutel bevat de naam van zijn werkruimte (
ask_api_{workspace}.{secret}), dus je hoeft geen werkruimte te kiezen. Bewaar hem in een secret store, nooit in de code.
Ga verder met Een endpoint aanroepen.
Verbinden als gebruiker: inloggen
Gebruik een apart account, niet dat van een persoon. Met het token van een ingelogd account kun je alles wat die gebruiker in de app kan, niet alleen de Data API aanroepen. Geef het account dus de toegekende rol en verder niets, en bewaar het wachtwoord in een secret store. Voor een service is een API-sleutel de betere keuze.
Stuur het e-mailadres en het wachtwoord van het account met een POST naar /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": "..."}' Het antwoord bevat de tokens:
{
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
"refreshToken": "q8Zr0dW3...",
"idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
"expiresIn": 900,
"redirectHost": null
} -
tokenis het access token. Stuur het mee bij elke aanroep. Het verloopt naexpiresInseconden, standaard 15 minuten. -
Met
refreshTokenkrijg je een nieuw paar zonder het wachtwoord (zie Een gebruikerssessie in leven houden). Het is standaard 30 dagen geldig en werkt precies één keer. idTokenenredirectHostzijn voor de browser-app, die kun je negeren.
Een geweigerde login antwoordt 401 met een code:
invalid_credentials, disabled (het account is uitgeschakeld) of
no_tenant (het account hoort bij geen enkele actieve werkruimte). Na te veel
mislukte pogingen volgt 429 met locked_out. Inloggen is bovendien
beperkt tot 20 pogingen per minuut per IP-adres, dus log één keer in en houd de sessie aan, in
plaats van voor elke aanroep opnieuw in te loggen.
Een endpoint aanroepen
Zet de API-sleutel of het access token in de Authorization-header; hieronder staat
$TOKEN voor een van beide. Een lijstaanroep geeft één pagina items terug, plus het
totaal:
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
} De veldnamen zijn de namen die de admin in deze versie heeft gekozen, en die hoeven niet overeen te komen met de kolommen in de database. Eén item lezen, aanmaken, wijzigen en verwijderen gaat zo:
# 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 wijzigt alleen de velden in de body. PUT doet hetzelfde, maar weigert een body waarin een verplicht veld ontbreekt.
-
Het id komt altijd uit de URL. Een
idin de body bij het aanmaken wordt genegeerd, dus aanmaken overschrijft nooit een bestaand item. -
Alleen-lezen velden in een body worden genegeerd, dus je kunt een item precies zo terugsturen
als je het hebt opgehaald. Een veld dat de versie niet kent, wordt geweigerd met
400, zodat typfouten meteen opvallen. - Schrijfacties gedragen zich precies als opslaan in de app: business events worden getriggerd, validatie draait en open schermen worden ververst.
Een script module aanroepen
Sommige endpoints draaien een script module in plaats van de rijen van een entity te leveren, voor werk dat meer is dan lezen of schrijven: de prijs van een order berekenen, een tijdslot boeken, een rapport. Elke versie van zo'n endpoint antwoordt op één methode, en het OpenAPI-document zegt welke: POST neemt de parameters als JSON-object in de body, GET neemt ze uit de querystring.
# 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" Het antwoord is wat de module teruggeeft, als JSON:
{
"quantity": 3,
"total": 172.5,
"deliveryDate": "2026-09-28"
} -
De parameternamen zijn de namen die de admin de versie gaf. Elke waarde wordt tegen haar type
gecontroleerd voordat de module draait: geheel getal, getal, true of false, een datum met of
zonder tijd (
2026-09-25,2026-09-25T14:30:00Z), of tekst. Een waarde van het verkeerde type, een parameter die de versie niet kent en een ontbrekende verplichte parameter geven een400;nulltelt als weggelaten. -
Een module die niets teruggeeft, antwoordt
204 No Content. Een module die faalt, antwoordt500, met de eigen melding van de module indetail. - Aanroepen vraagt een rol die op het endpoint de bewerking Call kreeg. Een module die de admin als admin only markeerde, antwoordt alleen admin en owner, dus een API-sleutel bereikt haar nooit.
- De module draait echt, precies zoals wanneer de app haar draait: wat ze schrijft, triggert business events, en mail die ze verstuurt, gaat echt de deur uit.
-
Aanroepen met de andere methode, of op een itempad zoals
/quote/1, geeft een405waarvan deAllow-header de methode noemt die je moet gebruiken.
Een gebruikerssessie in leven houden
Dit geldt alleen voor een ingelogde gebruiker, een API-sleutel heeft er niets van nodig.
Voordat het access token verloopt, of wanneer een aanroep 401 antwoordt, ruil je
het refresh token in voor een nieuw paar:
curl -X POST "$API/api/v1/auth/refresh" \
-H "Content-Type: application/json" \
-d '{"refreshToken": "q8Zr0dW3..."}' Het antwoord heeft dezelfde vorm als bij het inloggen, met een nieuw refresh token. Bewaar dat en gooi het oude weg: een refresh token werkt maar één keer. Wie een verbruikt token na meer dan een minuut opnieuw aanbiedt, wordt behandeld als een dief: de hele sessie wordt beëindigd en je moet opnieuw inloggen. Geef daarom elk proces of elke worker een eigen sessie, en deel nooit één refresh token tussen meerdere.
Een refresh antwoordt 401 wanneer de sessie voorbij is: ze is verlopen, het
account is uitgeschakeld of uit de werkruimte verwijderd, of er is uitgelogd. Log in dat geval
opnieuw in. Rolwijzigingen komen bij de volgende refresh in het token terecht, dus een
ingetrokken rol werkt binnen 15 minuten niet meer. Zo beëindig je zelf een sessie:
curl -X POST "$API/api/v1/auth/logout" \
-H "Content-Type: application/json" \
-d '{"refreshToken": "q8Zr0dW3..."}' Een account in meer dan één werkruimte
Bij het inloggen wordt één werkruimte voor de sessie gekozen: de werkruimte waartoe het adres
dat je aanroept behoort, als het account daar lid is, en anders de werkruimte die het laatst
gebruikt werd. Daarom hoort een apart integratieaccount maar bij één werkruimte. Hoort het bij
meerdere, vraag ze dan op en wissel met de tenantId van de werkruimte die je wilt:
# 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-..."}' Het wisselen geeft een nieuw tokenpaar voor die werkruimte terug. Ga daarmee verder zoals na het inloggen.
Filteren, sorteren en pagineren
# 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 | Betekenis |
|---|---|
page | Paginanummer, beginnend bij 1. |
pageSize | Items per pagina, standaard 50. Elke versie stelt een maximum in (100, tenzij de admin dat heeft gewijzigd); wie meer vraagt, krijgt een 400. |
sort | Eén veld, met een - ervoor voor aflopende volgorde. Zonder deze parameter komen de nieuwste eerst. |
search | Tekst die wordt gezocht in elk veld dat de versie teruggeeft. |
filter[field] | Gelijk aan de waarde. |
filter[field][op] | Vergeleken met een operator, zie hieronder. Bij meerdere filters moeten ze allemaal kloppen. |
| Operator | Komt overeen met |
|---|---|
eq, ne | Gelijk, niet gelijk. |
gt, gte, lt, lte | Groter of kleiner dan. Getallen, datums en tekst. |
like, startswith, endswith | Tekst die de waarde bevat, ermee begint of ermee eindigt, ongeacht hoofdletters. Alleen tekstvelden. |
in | Een van de waarden in een kommagescheiden lijst: filter[id][in]=1,2,3. |
null | true voor lege waarden, false voor ingevulde. |
Waarden worden gecontroleerd tegen het type van het veld, dus filter[creditLimit]=lots
geeft een 400 die het veld noemt. Je kunt alleen filteren, sorteren en zoeken op
velden die de versie teruggeeft, en een onbekende parameter wordt geweigerd in plaats van
genegeerd, zodat een typfout nooit stilletjes alles teruggeeft. Parameters die met
_ beginnen worden genegeerd, voor cache busting.
Versies
De versie maakt deel uit van het pad, en meerdere versies van één endpoint kunnen naast elkaar live staan. Een gepubliceerde versie verandert nooit op een manier die jouw integratie breekt: velden worden nooit verwijderd of hernoemd, en niets wordt verplicht. Een versie die een script module aanroept, houdt zo ook haar methode en parameters, en krijgt er alleen optionele bij. Zulke wijzigingen komen in een nieuwe versie.
- Versies met de status Active en Deprecated geven antwoord.
- Een versie die niet bestaat, of nog een Draft is, antwoordt
404en somt de versies op die wel bestaan. - Een Retired versie antwoordt
410 Goneen noemt de versie waarnaar je moet overstappen.
Elk antwoord vermeldt welke versies er bestaan. Een versie die Deprecated is, meldt dat ook, en wanneer ze verdwijnt:
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 is het moment waarop de versie op Deprecated werd gezet (een
Unix-tijd), Sunset de datum waarop ze niet meer antwoordt, als de admin die heeft
ingesteld, en Link verwijst naar de volgende versie. Een waarschuwing loggen zodra
Deprecation verschijnt, is de eenvoudigste manier om er op tijd van te horen.
Fouten
Een weigering van de Data API is een standaard probleemdocument (RFC 7807), waarvan
detail zegt wat er mis was:
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
{
"title": "Forbidden",
"status": 403,
"detail": "None of your roles may Delete on 'customers'."
}
Eén uitzondering: wanneer de validatie van een business event een schrijfactie weigert, is de
body van de 422 { "message": "...", "errorColumns": [...] },
met de foute velden onder de kolomnamen die de database zelf gebruikt.
| Status | Betekenis |
|---|---|
400 | Het verzoek is ongeldig: een onbekend veld of een onbekende parameter, een waarde van het verkeerde type, een ontbrekend verplicht veld. |
401 | Geen token of sleutel, of die is niet meer geldig. Een gebruiker doet een refresh en probeert opnieuw. Bij een API-sleutel is die verlopen of geroteerd, of is de client uitgeschakeld of verwijderd. |
403 | Geen van de rollen van het account mag dit doen op dit endpoint. |
404 | Dit endpoint, deze versie of dit item bestaat niet. |
405 | Deze versie biedt de bewerking niet aan, bv. verwijderen, of een endpoint van een script module werd met de andere methode aangeroepen. |
409 | De data conflicteert: een unieke waarde bestaat al, of andere records verwijzen nog naar dit record. |
410 | De versie is buiten gebruik gesteld (Retired). |
422 | Een bedrijfsregel of een tabelregel heeft de waarden geweigerd. |
429 | Te veel verzoeken. Wacht even en probeer opnieuw. |
500 | De script module achter een endpoint faalde; detail bevat haar melding. |
OpenAPI-beschrijving
GET /api/data/openapi.json, aangeroepen met je token, geeft een OpenAPI 3-document
terug met precies de versies en bewerkingen die jouw account mag gebruiken, met elk veld en
zijn type. Importeer het in Postman of Insomnia, of genereer er een getypeerde client mee.
Haal het opnieuw op nadat de admin een nieuwe versie heeft gepubliceerd.
Voorbeelden in Python
Met een API-sleutel is de header alles wat je nodig hebt:
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"])
Wie als gebruiker inlogt, moet de sessie zelf onderhouden. Dit script logt in, doet een refresh
een minuut voordat het token verloopt, herstelt van een 401, leest alle pagina's
en maakt een item aan. Zet het adres en de inloggegevens in omgevingsvariabelen.
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-omgevingen: daar kunnen alleen het account van de eigenaar van de trial en API clients die de eigenaar aanmaakt de API aanroepen.
Waar nu naartoe
- Business Entities - waar een endpoint uit leest, en waarom een entity die een one-to-many-tabel joint één item per gejoinde rij teruggeeft.
- User Management - maak het integratieaccount en zijn rol aan.