Authentification
Deux sortes d’habilitation, ce que chacune ouvre, et pourquoi une seule peut atteindre un navigateur.
Tout appel à l’api porte une habilitation, dans l’en-tête Authorization.
Authorization: Bearer <credential>
Il en existe deux sortes. Les confondre est l’erreur la plus coûteuse que l’on puisse commettre avec cette api.
D’abord, choisissez l’adresse. L’api existe une fois par environnement, et une habilitation délivrée pour l’un est refusée par l’autre. Les exemples de cette page visent la Production.
| Environnement | Base de l'API |
|---|---|
| Production | https://api.saphere.ai |
| Test | https://api.test.saphere.ai |
Les deux environnements sont complètement séparés : comptes distincts, clés distinctes, données distinctes. Voir Environnements.
La clé d’API
Votre clé d’API identifie votre compte. C’est un simple identifiant unique.
POST /access-tokens HTTP/1.1
Host: api.saphere.ai
Authorization: Bearer 2428fbbc-d322-49cb-90a7-4e93401e67f8
Content-Type: application/json
Trois choses à son sujet méritent d’être dites franchement.
- Elle n’a aucune limite. Elle ouvre toutes les routes de l’api : créer des mesures, les relire, les supprimer, délivrer des jetons.
- Elle n’expire pas. Rien dans la clé elle-même ne borne sa durée de validité.
- La seule chose qui l’arrête est la désactivation de votre compte. Révoquer une clé revient à désactiver le compte, ce qui coupe au même instant toutes les intégrations qui l’utilisent.
La clé ne doit jamais atteindre un navigateur ni un paquet mobile
Une clé livrée dans du code client est une clé que n’importe qui peut lire : en ouvrant les outils de développement du navigateur, en décompressant une application mobile, ou en récupérant vos fichiers depuis un cache. Et comme la révoquer revient à désactiver le compte, une fuite n’est pas un incident mineur.
Le widget est conçu pour n’avoir jamais besoin de la clé. Il demande à votre serveur un jeton de courte durée à la place. Prenez cette voie.
Les jetons d’accès
Un jeton d’accès est l’habilitation que vous pouvez remettre à un navigateur. Votre serveur le crée à partir de votre clé d’API, avec POST /access-tokens. Il est délibérément faible.
| Clé d’API | Jeton d’accès | |
|---|---|---|
| Portée | Tout | Uniquement les permissions qu’il nomme |
| Expiration | Aucune | Obligatoire, 48 heures au plus |
| Rejouable | Toujours | Seulement si vous l’autorisez |
| Révocation | Désactiver le compte | DELETE /access-tokens/{id} |
| Sûr dans un navigateur | Non | Oui, par conception |
Deux permissions existent : measurement.scan, la mesure du widget Saphere Scan, et measurement.realtime, celle de la chaîne temps réel. Elles autorisent l’ouverture d’une session de mesure, et rien d’autre. Un jeton qui en porte une ne peut pas lire vos mesures, ne peut rien supprimer, et ne peut pas créer d’autres jetons.
Donnez-lui maxSessions: 1 et il ouvre exactement une session. C’est ce qu’une intégration par widget doit utiliser : un jeton consommé par la mesure pour laquelle il a été créé, et sans valeur ensuite.
La valeur du jeton et son identifiant sont deux chaînes différentes
POST /access-tokens rend les deux. value est l’habilitation. Elle commence par ivat-, elle n’est rendue qu’une seule fois, et c’est elle que le widget présente. id nomme le jeton : c’est ce que vous passez pour le révoquer, et l’employer comme habilitation n’ouvre rien.
Seule une empreinte de value est conservée, de sorte qu’une copie de la base ne distribue aucun jeton utilisable.
Limitation de débit
Les requêtes sont comptées par habilitation et par route, sur une minute glissante. Chaque route tient un compte à elle, de sorte qu’une rafale sur l’une n’en ferme jamais une autre.
| Route | Limite |
|---|---|
POST /access-tokens | 20 par minute |
| Tout le reste | 300 par minute |
La création de jetons est délibérément plus serrée que le reste. Chaque appel écrit en base, et un jeton est censé être demandé une fois, au début d’une mesure. Jamais en boucle.
Ces bornes sont dimensionnées pour les pointes plutôt que pour le régime courant : elles sont là pour qu’une rafale ne devienne pas une panne. Ce qui gouverne votre volume est votre quota, pas ce tableau.
Les réponses portent X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset, ce dernier en secondes. Dépasser une limite donne un 429, avec un en-tête Retry-After en secondes : la route reste fermée à cette habilitation pendant une minute au plus.
L’ouverture d’une mesure
La poignée de main qui ouvre une mesure Saphere Scan — qu’elle présente un jeton d’accès ou un identifiant de mesure — est comptée à part, par client et non par route :
| Ce qui est compté | Limite |
|---|---|
| mesures ouvertes | 2 par seconde |
| mesures ouvertes | 20 par minute |
Les deux fenêtres vont ensemble : la seconde étale ce que la minute autorise, une mesure occupant de la mémoire tant qu’elle dure. Une ouverture refusée par cette borne ne consomme pas le jeton d’accès qu’elle présentait : il reste présentable, et une nouvelle tentative un instant plus tard aboutit.
Ces valeurs laissent une marge large sur ce qu’un parcours utilisateur produit — une personne ne lance pas deux mesures dans la même seconde. Elles se rencontrent en automatisant : un banc d’essai qui enchaîne les mesures doit les espacer.
Un diagnostic utile
Si une réponse porte les en-têtesX-RateLimit-*, la requête a atteint une route et a été refusée par un contrôle. Si un 404 n’en porte aucun — et que son corps est le texte brut Not Found —, aucune route n’a correspondu : c’est le chemin qui est faux, plutôt que la ressource.Réponses d’erreur
Les erreurs sont en JSON, avec une exception qu’il faut connaître.
{
"statusCode": 400,
"message": ["duration must be an interval amounting to more than zero, such as 2h 30min, 45min or 90s, each unit appearing at most once, and to at most 48h"],
"error": "Bad Request"
}
Un chemin qui ne correspond à aucune route répond en texte brut : un 404 dont le corps est Not Found, sans JSON. Toute autre erreur, y compris le 404 d’une ressource inexistante, est en JSON — { "message": "Not Found", "statusCode": 404 }. Si votre code analyse les corps d’erreur comme du JSON, regardez d’abord le Content-Type, sinon un chemin mal saisi échouera à l’analyse au lieu d’être traité par son statut.
| Statut | Signification |
|---|---|
400 | Le corps ou la requête a échoué à la validation ; message énumère quoi. |
401 | En-tête Authorization absent ou mal formé, clé d’API inconnue, ou compte désactivé. |
404 | Ressource inexistante, ou qui n’est pas la vôtre. En texte brut seulement quand aucune route ne correspond au chemin. |
406 | Votre en-tête Accept exclut le JSON sur une route qui répond en JSON. Envoyez application/json ou */*. |
415 | Une route qui prend un corps JSON a reçu un autre Content-Type. |
429 | Limite de débit dépassée ; Retry-After indique quand revenir. |
500 | La requête n’a pas pu aboutir de notre côté. |
503 | L’instance déleste ; Retry-After indique quand revenir. |
Introuvable et pas à vous sont la même réponse
Une ressource appartenant à un autre compte rend exactement le même404 qu’une ressource qui n’a jamais existé. C’est délibéré : personne ne peut se servir de l’api pour savoir si un identifiant est réel.