Guide · 15 min

Se connecter à la Data API

La Data API expose les Business Entities sous forme d'endpoints REST versionnés, sous /api/data/v{version}/{route}, par exemple /api/data/v1/customers. Un administrateur décide quelles entités sont exposées, quels champs chaque version propose, et quels rôles peuvent lister, lire, créer, modifier ou supprimer. Un endpoint peut aussi appeler un Script Module : il prend des paramètres et répond avec ce que le module renvoie. Ce guide s'adresse à la personne qui écrit l'intégration : comment se connecter, en tant que service avec une clé d'API ou en tant qu'utilisateur qui ouvre une session, et comment appeler les endpoints.

Avant de commencer

Demandez quatre choses à l'administrateur de l'espace de travail :

  1. L'adresse de l'API. C'est le même hôte que celui auquel l'application elle-même s'adresse. Dans l'écran API Endpoints, le panneau Try l'affiche dans sa ligne curl.
  2. L'endpoint et la version, par exemple customers version 1. Seule une version au statut Active ou Deprecated répond ; une version en Draft ne répond pas.
  3. Un rôle autorisé sur cet endpoint pour les opérations dont vous avez besoin, défini dans la grille Who can call it de l'endpoint. Les rôles admin et owner peuvent toujours tout appeler.
  4. Un accès qui dispose de ce rôle : une clé d'API pour un service, ou un compte utilisateur pour une personne ou un script ponctuel.

Pour tout ce qui tourne sans surveillance, comme une synchronisation nocturne, une boutique en ligne ou un outil de reporting, utilisez une clé d'API. Elle ne demande ni mot de passe, ni connexion, ni rafraîchissement, et elle ouvre la Data API et rien d'autre.

Connecter un service avec une clé d'API

Un administrateur crée un API client dans l'écran API Clients : un nom, les rôles qu'il détient et, en option, une date d'expiration. Les rôles admin et owner ne peuvent pas être attribués à un client, qui n'atteint donc que ce que ses rôles sont autorisés à faire sur chaque endpoint. La clé n'est affichée qu'une seule fois, à la création du client ; seule une empreinte en est conservée, si bien que personne ne peut la retrouver par la suite, pas même un administrateur.

Envoyez la clé comme jeton bearer à chaque appel :

API=https://api.example.com
KEY=ask_api_acme.q7Vt0r2Lk_dP9sXw3eNfH1mA6yZc4uBgJ8oTiR5vWnE

curl "$API/api/data/v1/customers" -H "Authorization: Bearer $KEY"
  • La clé ne fonctionne que sous /api/data. Partout ailleurs dans le produit, elle est refusée : une clé divulguée ne peut donc atteindre ni les autres données de l'application, ni ses paramètres.
  • Elle reste valable jusqu'à ce qu'elle expire, que le client soit désactivé ou supprimé, ou qu'un administrateur en fasse la rotation pour en obtenir une nouvelle. Chacun de ces changements prend effet dès l'appel suivant, qui répond alors 401.
  • Les modifications effectuées par le service portent le nom du client comme auteur, et les Business Events le voient comme l'utilisateur.
  • La clé indique son espace de travail (ask_api_{workspace}.{secret}), si bien qu'aucun choix d'espace de travail n'est nécessaire. Conservez-la dans un gestionnaire de secrets, jamais dans le code.

Continuez avec Appeler un endpoint.

Se connecter en tant qu'utilisateur : ouvrir une session

Utilisez un compte dédié, pas celui d'une personne. Un jeton obtenu à la connexion peut faire tout ce que cet utilisateur peut faire dans l'application, pas seulement appeler la Data API : donnez donc au compte le rôle autorisé et rien d'autre, et conservez son mot de passe dans un gestionnaire de secrets. Pour un service, une clé d'API est le meilleur choix.

Envoyez l'email et le mot de passe du compte en POST à /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": "..."}'

La réponse contient les jetons :

{
  "token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
  "refreshToken": "q8Zr0dW3...",
  "idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
  "expiresIn": 900,
  "redirectHost": null
}
  • token est le jeton d'accès. Envoyez-le à chaque appel. Il expire au bout de expiresIn secondes, 15 minutes par défaut.
  • refreshToken permet d'obtenir une nouvelle paire sans le mot de passe (voir Maintenir une session utilisateur active). Il est valable 30 jours par défaut et ne sert qu'une seule fois.
  • idToken et redirectHost servent à l'application dans le navigateur ; ignorez-les.

Une connexion refusée répond 401 avec un code : invalid_credentials, disabled (le compte est désactivé) ou no_tenant (le compte n'appartient à aucun espace de travail actif). Trop de tentatives échouées répondent 429 avec locked_out. La connexion est en outre limitée à 20 tentatives par minute et par adresse IP : connectez-vous donc une fois et conservez la session, plutôt que de vous reconnecter à chaque appel.

Appeler un endpoint

Placez la clé d'API ou le jeton d'accès dans l'en-tête Authorization ; ci-dessous, $TOKEN désigne l'un ou l'autre. Un appel de liste renvoie une page d'éléments et le total :

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
}

Les noms de champs sont ceux que l'administrateur a donnés à cette version, et ils ne correspondent pas forcément aux colonnes de la base de données. Lire un élément, en créer, en modifier et en supprimer se fait ainsi :

# 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 ne modifie que les champs présents dans le corps. PUT fait de même, mais refuse un corps qui omet un champ obligatoire.
  • L'id vient toujours de l'URL. Un id dans le corps d'une création est ignoré, si bien qu'une création n'écrase jamais un élément existant.
  • Les champs en lecture seule présents dans un corps sont ignorés, vous pouvez donc renvoyer un élément exactement tel que vous l'avez récupéré. Un champ que la version ne connaît pas est refusé avec 400, ce qui attrape les fautes de frappe.
  • Les écritures se comportent exactement comme un enregistrement dans l'application : les Business Events se déclenchent, la validation s'exécute et les écrans ouverts se rafraîchissent.

Appeler un Script Module

Certains endpoints exécutent un Script Module au lieu de servir les lignes d'une entité, pour un travail qui dépasse une lecture ou une écriture : calculer le prix d'une commande, réserver un créneau, produire un rapport. Chaque version d'un tel endpoint répond à une seule méthode, et le document OpenAPI indique laquelle : POST prend les paramètres sous forme d'objet JSON dans le corps, GET les prend dans la chaîne de requête.

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

La réponse est ce que le module renvoie, en JSON :

{
  "quantity": 3,
  "total": 172.5,
  "deliveryDate": "2026-09-28"
}
  • Les noms des paramètres sont ceux que l'administrateur a donnés à la version. Chaque valeur est vérifiée par rapport à son type avant l'exécution du module : nombre entier, nombre, true ou false, une date avec ou sans heure (2026-09-25, 2026-09-25T14:30:00Z), ou du texte. Une valeur du mauvais type, un paramètre que la version ne connaît pas et un paramètre obligatoire manquant donnent un 400 ; un null compte comme omis.
  • Un module qui ne renvoie rien répond 204 No Content. Un module qui échoue répond 500, avec son propre message dans detail.
  • L'appel exige un rôle auquel l'opération Call a été accordée sur l'endpoint. Un module que l'administrateur a marqué admin only ne répond qu'aux rôles admin et owner : une clé d'API ne l'atteint donc jamais.
  • Le module s'exécute pour de vrai, exactement comme quand l'application l'exécute : ce qu'il écrit déclenche les Business Events, et les e-mails qu'il envoie partent vraiment.
  • Un appel avec l'autre méthode, ou sur un chemin d'élément comme /quote/1, donne un 405 dont l'en-tête Allow indique la méthode à utiliser.

Maintenir une session utilisateur active

Ceci ne concerne qu'un utilisateur connecté ; une clé d'API n'a besoin de rien de tout cela. Avant que le jeton d'accès n'expire, ou quand un appel répond 401, échangez le jeton de rafraîchissement contre une nouvelle paire :

curl -X POST "$API/api/v1/auth/refresh" \
  -H "Content-Type: application/json" \
  -d '{"refreshToken": "q8Zr0dW3..."}'

La réponse a la même forme que celle de la connexion, avec un nouveau jeton de rafraîchissement. Conservez-le et jetez l'ancien : un jeton de rafraîchissement ne sert qu'une fois. Présenter de nouveau un jeton déjà utilisé, plus d'une minute après son utilisation, est considéré comme un vol et met fin à toute la session, après quoi vous devez vous reconnecter. Donnez donc à chaque processus ou worker sa propre session, et ne partagez jamais un jeton de rafraîchissement entre eux.

Un rafraîchissement répond 401 quand la session a pris fin : elle a expiré, le compte a été désactivé ou retiré de l'espace de travail, ou la session a été fermée par une déconnexion. Reconnectez-vous dans ce cas. Les changements de rôle atteignent le jeton au rafraîchissement suivant, donc un rôle retiré cesse de fonctionner en 15 minutes au plus. Pour mettre fin vous-même à une session :

curl -X POST "$API/api/v1/auth/logout" \
  -H "Content-Type: application/json" \
  -d '{"refreshToken": "q8Zr0dW3..."}'

Un compte dans plusieurs espaces de travail

La connexion choisit un espace de travail pour la session : celui auquel appartient l'adresse que vous appelez si le compte en est membre, sinon le dernier qu'il a utilisé. C'est pourquoi un compte dédié ne devrait appartenir qu'à un seul espace de travail. S'il appartient à plusieurs, listez-les et basculez avec le tenantId de celui que vous voulez :

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

Le changement renvoie une nouvelle paire de jetons pour cet espace de travail ; continuez avec elle comme après une connexion.

Filtrer, trier et paginer

# 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"
ParamètreSignification
pageNuméro de page, à partir de 1.
pageSizeÉléments par page, 50 par défaut. Chaque version fixe un maximum (100, sauf si l'administrateur l'a modifié) ; en demander davantage donne un 400.
sortUn seul champ, précédé de - pour l'ordre décroissant. Sans ce paramètre, les plus récents d'abord.
searchTexte recherché dans tous les champs que la version renvoie.
filter[field]Égal à la valeur.
filter[field][op]Comparé à l'aide d'un opérateur, voir ci-dessous. Quand il y a plusieurs filtres, ils doivent tous correspondre.
OpérateurCorrespond à
eq, neÉgal, différent.
gt, gte, lt, lteSupérieur ou inférieur à. Nombres, dates et texte.
like, startswith, endswithTexte qui contient la valeur, commence ou se termine par elle, sans tenir compte de la casse. Champs texte uniquement.
inL'une des valeurs d'une liste séparée par des virgules : filter[id][in]=1,2,3.
nulltrue pour les valeurs vides, false pour les valeurs renseignées.

Les valeurs sont vérifiées selon le type du champ, donc filter[creditLimit]=lots donne un 400 qui nomme le champ. Seuls les champs que la version renvoie peuvent servir à filtrer, trier ou rechercher, et un paramètre inconnu est refusé plutôt qu'ignoré, pour qu'une faute de frappe ne renvoie jamais tout sans rien dire. Les paramètres qui commencent par _ sont ignorés, ce qui permet le cache busting.

Versions

La version fait partie du chemin, et plusieurs versions d'un même endpoint peuvent être en service côte à côte. Une version publiée ne change jamais d'une manière qui casserait votre intégration : aucun champ n'est supprimé ni renommé, et rien ne devient obligatoire. De même, une version qui appelle un Script Module garde sa méthode et ses paramètres, et n'en gagne que d'optionnels. Les changements de ce type arrivent sous la forme d'une nouvelle version.

  • Les versions Active et Deprecated répondent.
  • Une version qui n'existe pas, ou qui est encore en Draft, répond 404 et liste les versions qui existent.
  • Une version Retired répond 410 Gone et indique celle vers laquelle migrer.

Chaque réponse indique quelles versions existent. Une version Deprecated le signale aussi, ainsi que la date de son retrait :

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 est le moment où elle a été dépréciée (un horodatage Unix), Sunset la date à laquelle elle cessera de répondre, si l'administrateur en a fixé une, et Link pointe vers la version suivante. Journaliser un avertissement chaque fois que Deprecation apparaît est le moyen le plus simple d'être prévenu à temps.

Erreurs

Un refus de la Data API est un document Problem Details standard (RFC 7807) dont le champ detail indique ce qui n'allait pas :

HTTP/1.1 403 Forbidden
Content-Type: application/problem+json

{
  "title": "Forbidden",
  "status": 403,
  "detail": "None of your roles may Delete on 'customers'."
}

Une exception : quand la validation d'un Business Event refuse une écriture, le corps de la réponse 422 est { "message": "...", "errorColumns": [...] }, qui nomme les champs en cause avec les noms de colonnes propres à la base de données.

StatutSignification
400La requête est mal formée : un champ ou un paramètre inconnu, une valeur du mauvais type, un champ obligatoire manquant.
401Aucun jeton ni clé, ou celui fourni n'est plus valide. Un utilisateur rafraîchit son jeton et réessaie ; une clé d'API a expiré, a fait l'objet d'une rotation, ou son client a été désactivé ou supprimé.
403Aucun des rôles du compte n'a le droit de faire cela sur cet endpoint.
404Endpoint, version ou élément introuvable.
405Cette version ne propose pas l'opération, par exemple la suppression, ou un endpoint de Script Module a été appelé avec l'autre méthode.
409Les données sont en conflit : une valeur unique existe déjà, ou d'autres enregistrements font encore référence à celui-ci.
410La version a été retirée.
422Une règle métier ou une règle de la table a refusé les valeurs.
429Trop de requêtes. Patientez un peu et réessayez.
500Le Script Module derrière un endpoint a échoué ; detail contient son message.

Description OpenAPI

GET /api/data/openapi.json, appelé avec votre jeton, renvoie un document OpenAPI 3 qui décrit exactement les versions et les opérations que votre compte peut utiliser, avec chaque champ et son type. Importez-le dans Postman ou Insomnia, ou générez-en un client typé. Récupérez-le de nouveau quand l'administrateur publie une nouvelle version.

Exemples en Python

Avec une clé d'API, l'en-tête suffit :

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

Avec un utilisateur connecté, la session demande un peu plus d'attention. Cet exemple se connecte, rafraîchit le jeton une minute avant son expiration, se rétablit après un 401, lit toutes les pages et crée un élément. Placez l'adresse et les identifiants dans des variables d'environnement.

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"))
Environnements d'essai : seuls le compte du propriétaire de l'essai et les API clients qu'il crée peuvent y appeler l'API.

Pour aller plus loin

  • Business Entities - ce que lit un endpoint, et pourquoi une entité qui joint une table en un-à-plusieurs renvoie un élément par ligne jointe.
  • User Management - créez le compte d'intégration et son rôle.