Jetons d'accès
Émettre l’habilitation courte et cadrée que le widget présente — et la révoquer.
Un jeton d’accès est ce que vous remettez à un navigateur à la place de votre clé d’API. Il nomme ce qu’il a le droit de faire, il expire, et vous pouvez le rendre à usage unique.
Créer un jeton
POST /access-tokens
Authorization: Bearer <apiKey>
Content-Type: application/json
{
"description": "Mobile application, iOS and Android",
"permissions": [{ "name": "measurement.scan", "maxSessions": 1 }],
"duration": "15min"
}
Champs de la requête
| Champ | Type | Requis | Remarques |
|---|---|---|---|
description | chaîne | non | Texte libre, jusqu’à 255 caractères. Uniquement là pour vous aider à reconnaître le jeton plus tard. |
permissions | tableau | oui | Au moins une entrée. Nommer deux fois la même permission est refusé. |
duration | chaîne | oui | Un intervalle. 48h au plus, et strictement positif. Jusqu’à 64 caractères. |
Permissions
Deux permissions existent, une par chaîne de mesure.
{ "name": "measurement.scan", "maxSessions": 1 }
| Champ | Type | Remarques |
|---|---|---|
name | "measurement.scan" ou "measurement.realtime" | Ce que le jeton ouvre : une session du widget Saphere Scan, dont le résultat est calculé à la fin, ou une session de la chaîne temps réel, dont les grandeurs redescendent pendant la mesure. |
maxSessions | entier ≥ 1, ou null | Nombre de sessions que le jeton peut ouvrir. |
Ce sont deux portes distinctes : un jeton frappé pour l’une ne se fait pas accepter par l’autre. Un même jeton peut porter les deux, en les demandant toutes les deux — nommer deux fois la même reste refusé.
`maxSessions` n'a pas de valeur par défaut
Omettez-le — ou envoyez null — et le jeton ne porte aucune limite sur le nombre de sessions qu’il ouvre. Sa durée devient la seule chose qui le borne, et il est rejouable jusqu’à son expiration.
C’est un choix légitime, mais il doit être délibéré. Pour une intégration par widget, écrivez maxSessions: 1 et émettez un jeton neuf pour chaque mesure.
Une session est comptée à son ouverture, non à l’aboutissement d’une mesure — une reconnexion à une session déjà ouverte ne compte pas. Un utilisateur qui ouvre la caméra puis s’en va a consommé le jeton. C’est pourquoi le widget en demande un nouveau au début de chaque tentative plutôt que de le mettre en cache.
Durées
duration s’écrit comme une durée : un nombre, une unité, et éventuellement d’autres à la suite.
30s 15min 2h 48h
2h 30min 1d 1d 12h
Unités acceptées :
| Unité | Écriture |
|---|---|
| Secondes | s, sec, secs, second, seconds |
| Minutes | m, min, mins, minute, minutes |
| Heures | h, hr, hrs, hour, hours |
| Jours | d, day, days |
`m` signifie minutes
Il ne signifie pas mois. Les mois s’écrivent mon. Cela suit la grammaire d’intervalle qu’emploie la base de données, et c’est la chose la plus susceptible d’être mal comprise ici.
En pratique, le piège se referme rarement : tout ce qui s’exprime en semaines ou en mois dépasse le plafond de 48 heures et est refusé d’emblée.
Deux règles supplémentaires :
- Une unité ne peut apparaître qu’une fois, toutes ses orthographes comptant comme la même unité.
1d 12hest valide ;1d 1dayne l’est pas. - Le plafond est de 48 heures. Un jeton existe pour la mesure qu’il ouvre, non pour la durée d’un contrat. Il est imposé à la fois lors de la validation de votre requête et de nouveau à l’enregistrement du jeton.
- Écrivez le plafond en heures :
48h, non2d. Les jours sont comptés sur le calendrier à l’enregistrement du jeton, où un jour ne dure pas toujours 24 heures. Une durée en jours qui dépasse alors le plafond passe la validation mais est refusée à ce moment-là — par un500plutôt qu’un400, aujourd’hui.
Réponse
201 Created
{
"id": "8f1c2d34-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
"value": "ivat-GPPctPJ3HURBcwEKJFq26Ckp4khJUeNp8qjhLK0RblozlBfP0",
"description": "Mobile application, iOS and Android",
"permissions": [{ "name": "measurement.scan", "maxSessions": 1 }],
"expiresAt": "2026-08-24T15:12:44.000Z",
"createdAt": "2026-08-24T14:57:44.000Z"
}
| Champ | Ce que c’est |
|---|---|
id | Nomme le jeton. À repasser pour le révoquer. N’ouvre rien. |
value | L’habilitation. Commence par ivat-. C’est elle que vous remettez au widget. |
description, permissions | Tels que vous les avez envoyés ; description vaut null si vous n’en avez pas envoyé. |
expiresAt | Résolu depuis la durée, par la base de données, à l’insertion. |
`value` n'est rendu qu'une seule fois
Seul son condensé est conservé. Aucune route ne peut vous le redonner. Si vous le perdez, révoquez le jeton et émettez-en un autre.
Il s’ensuit que value ne devrait pas être écrit dans vos journaux. id et expiresAt peuvent l’être sans risque — ils n’ouvrent rien.
expiresAt vaut exactement createdAt plus la durée demandée, résolue par la base de données plutôt que par votre horloge ou par la nôtre.
Révoquer un jeton
DELETE /access-tokens/{id}
Authorization: Bearer <apiKey>
200 OK en cas de succès, avec un corps vide. Le {id} est l’id de la réponse de création, non value.
Une seule réponse pour trois situations
Un identifiant qui n’a jamais existé, un jeton déjà révoqué et un jeton appartenant à un autre client rendent tous le même 404, { "message": "Not Found", "statusCode": 404 }. Personne ne peut sonder des jetons qui ne lui appartiennent pas.
Un identifiant mal formé rend 400 à la place : il est refusé par la validation avant toute recherche.
Un jeton expiré reste supprimable. L’expiration masque un jeton à toute lecture, mais elle ne retire pas la ligne, et faire le ménage derrière soi reste possible.
Exemple complet
Les commandes ci-dessous s’adressent à la Production. Les mêmes appels sur le Test ne diffèrent que par la base :
| Environnement | Base de l'API |
|---|---|
| Production | https://api.saphere.ai |
| Test | https://api.test.saphere.ai |
Un jeton émis sur l’un n’est jamais valable sur l’autre — voir Environnements.
# Émettre un jeton à usage unique, valable un quart d'heure
curl -sX POST https://api.saphere.ai/access-tokens \
-H "Authorization: Bearer $SAPHERE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "web widget",
"permissions": [{ "name": "measurement.scan", "maxSessions": 1 }],
"duration": "15min"
}'
# Le révoquer par anticipation
curl -sX DELETE https://api.saphere.ai/access-tokens/8f1c2d34-5a6b-4c7d-8e9f-0a1b2c3d4e5f \
-H "Authorization: Bearer $SAPHERE_API_KEY"
Quand un jeton est refusé
Le serveur de mesure refuse une poignée de main sans dire pourquoi — délibérément, pour qu’un jeton rejeté ne puisse pas être sondé pour en connaître le motif. Déroulez la liste :
| Vérification | Comment le savoir |
|---|---|
| Expiré ? | Comparez expiresAt à maintenant. Les jetons sont émis par mesure, et ce n’est pas un hasard. |
| Déjà consommé ? | Avec maxSessions: 1, ouvrir une session le consomme. Une nouvelle tentative demande un nouveau jeton. |
| Révoqué ? | Le DELETE est irréversible. |
| Client désactivé ? | Tous les jetons d’un client inactif cessent de fonctionner d’un coup. |
| Ouvertures trop rapprochées ? | Au-delà de deux ouvertures par seconde ou de vingt par minute pour votre compte, les ouvertures sont refusées de la même façon — sans consommer le jeton. Voir l’ouverture d’une mesure. |
id envoyé à la place de value ? | La cause la plus fréquente. id est un UUID ; value commence par ivat-. |
Relayer la réponse entière convient très bien
Si votre point d’accès relaie la réponse de l’API telle quelle, le widget y prélèvevalue de lui-même. Ne relayer que l’id est le bogue d’intégration classique — l’appel réussit, et toutes les mesures sont refusées.Limite de débit
POST /access-tokens est limité à 20 appels par minute et par clé d’API, plus serré que les 300 généraux. Émettez un jeton par mesure, au moment où la mesure démarre.