Démarrer
Demandez d'abord un bac à sable — recharge-sandbox, edvis-sandbox — et non votre accès de production : vous y déroulerez tout ce qui suit sans qu'une identité d'essai puisse atteindre un dossier réel.
L'administrateur d'AYAEL enregistre l'application depuis le back-office, page Applications. Ni accès au serveur, ni redémarrage : les vérifications en cours ne sont pas interrompues. Il vous remet alors trois choses.
| Ce qu'on vous remet | À quoi ça sert |
|---|---|
| Une clé de service | Identifie votre application, et détermine à quels dossiers vous avez accès. Transmise dans l'en-tête X-API-Key. |
| Un secret de signature | Sert à vérifier que les webhooks viennent bien d'AYAEL. Distinct de la clé, et inchangé quand celle-ci est renouvelée. |
| Une URL de webhook, chez vous | En HTTPS, obligatoire. AYAEL y annonce chaque changement de statut : sans elle, vous ne sauriez jamais qu'un dossier est approuvé. Elle se remplace, mais ne s'efface pas. |
La clé et le secret ne s'affichent qu'une fois. Notez-les à ce moment-là.
De la clé, la base ne garde qu'une empreinte : personne ne peut vous la relire, pas même l'administrateur. Égarée, elle se renouvelle — l'ancienne cessant d'être acceptée à l'instant même.
Le secret, lui, reste en base : AYAEL doit pouvoir signer. Mais l'interface cesse de l'afficher dès que sa copie est confirmée, et le régénérer fait refuser vos notifications tant que vous n'avez pas la nouvelle valeur.
L'identifiant de votre application (recharge-sandbox, mma…) devient son source_app : il apparaît dans le back-office et dans chaque webhook. Il est définitif, car c'est lui que portent les dossiers déjà enregistrés — et le nom d'affichage ne se modifie pas davantage aujourd'hui. Prévoyez-le parlant, surtout si vous aurez un bac à sable et un accès de production côte à côte.
La règle qui prime sur les autres
La clé de service ne part jamais dans une application mobile ni dans une page web. Elle vit sur votre serveur.
Une clé embarquée dans un binaire mobile est extractible — c'est une question d'outillage, pas de difficulté — et elle ouvre alors l'accès à tous les dossiers de votre application, pas seulement à celui de son porteur.
Votre application
Mobile ou web. Authentifiée par votre mécanisme habituel.
Votre serveur
Vérifie le jeton de son utilisateur, puis appelle AYAEL avec la clé.
AYAEL
Répond, puis notifie le résultat par webhook signé.
Ne dérivez jamais le user_id d'un paramètre envoyé par le client. Prenez-le du jeton que vous venez de vérifier, sinon n'importe qui peut ouvrir un dossier au nom d'un autre.
Deux cloisonnements, et un seul est assuré par AYAEL. AYAEL sépare les applications entre elles : votre clé ne donne accès qu'à vos dossiers, et celui d'une autre application répond 404. Séparer vos utilisateurs les uns des autres n'appartient qu'à vous — pour AYAEL, tous vos dossiers sont légitimement les vôtres, et il ne sait pas lequel de vos utilisateurs est derrière l'appel.
Or le case_id circule ensuite dans les URL — dépôt d'image, soumission, relecture — et c'est le client qui le fournit. Vérifiez à chaque appel qu'il appartient bien à l'utilisateur authentifié, par un GET /kyc/cases/{id} dont vous comparez le user_id au vôtre. Sans cela, un utilisateur authentifié dépose sa pièce dans le dossier d'un autre, ou fait décider un dossier qui n'est pas le sien. Prendre le user_id du jeton protège la création du dossier, pas les appels qui suivent.
Le parcours
Créer le dossier, déposer les images, finaliser, puis attendre le webhook. Toutes les requêtes portent l'en-tête X-API-Key.
Ouvre un dossier pour un utilisateur de votre application. Le corps ne porte que user_id : l'application est déduite de la clé. Conservez l'id renvoyé — mais le temps de cette tentative seulement : après une correction, la suivante demande un dossier neuf. Ce qui persiste chez vous, c'est le user_id.
Dépose une image en multipart/form-data, sous la clé file. Un appel par image.
Clôt la saisie et lance la vérification. Répond immédiatement.
État du dossier et détail des contrôles. Pour rattraper un webhook perdu — pas pour interroger en boucle.
Vos dossiers, du plus récent au plus ancien. De quoi alimenter votre propre back-office sans dupliquer la base.
Pour trancher ce que les contrôles automatiques ne tranchent pas — vos dossiers en UNDER_REVIEW. C'est votre file depuis le 01/10/2026. Le même geste existe dans votre espace, onglet Décision sur la fiche du dossier.
Deux cas vous échappent : un dossier dont le verdict de doublon vaut IDENTITY_CONFLICT ou FACE_REUSED est tranché par un opérateur d'AYAEL KYC (403). Ce qui les motive concerne une autre application — on ne peut pas vous la montrer, et juger sans voir n'aurait pas de sens. Vous gardez la lecture du dossier.
reviewer_id ne désigne ni votre application (elle est déduite de la clé) ni le client vérifié, mais l'opérateur humain qui tranche, chez vous — conservé tel quel dans le journal du dossier. Une approbation dit que les contrôles sont passés et que vous avez confirmé : faites donc trancher par des gens qui ne sont pas ceux qui inscrivent les clients.
Les images attendues
| kind | Rôle |
|---|---|
DOC_FRONT | Obligatoire. Seule image lue par l'OCR et comparée au selfie. |
DOC_BACK | Facultatif. Conservé au dossier, aucun contrôle ne le lit aujourd'hui. |
SELFIE | Obligatoire. Comparaison faciale avec le recto. |
LIVENESS_BEFORELIVENESS_DURINGLIVENESS_AFTER | Seulement si vous faites passer un défi de vivacité. |
Ces six valeurs sont les seules acceptées : toute autre répond 400. Le nom du fichier que vous envoyez n'est pas conservé — AYAEL nomme lui-même ce qu'il écrit, et se fie au Content-Type de la partie file pour l'extension.
Finaliser
POST /kyc/cases/{case_id}/submit
X-API-Key: ayl_recharge_…
{
"declared_data": {
"first_name": "Aya",
"last_name": "Kouassi",
"dob": "1992-04-17"
},
"document_type": "CNI",
"document_country": "CI",
"liveness_challenge": "BLINK"
}
dob au format AAAA-MM-JJ. Nom, prénom et date de naissance alimentent la détection de doublons et la comparaison avec la pièce : des champs vides affaiblissent les deux. liveness_challenge vaut BLINK ou TURN, et seulement si les trois images de vivacité ont été déposées.
Idempotent tant que la vérification tourne. Renvoyé pendant que le dossier est SUBMITTED — après une coupure réseau, quand vous ne savez pas si votre requête est arrivée — il répond 200 sans relancer la vérification ni produire une deuxième notification. Une fois le dossier vérifié, il répond 409 : voir « Reprendre après une correction ».
Statuts
La vérification dure de quelques secondes à deux minutes. Le résultat arrive par webhook.
| Statut | Sens | Ce que vous en faites |
|---|---|---|
| IN_PROGRESS | Dossier ouvert, rien de soumis | Rien |
| SUBMITTED | Soumis, vérification automatique en cours | Afficher une attente |
| UNDER_REVIEW | Contrôles terminés, un opérateur doit trancher | Afficher une attente — le client n'a rien à refaire |
| APPROVED | Identité vérifiée | Débloquer l'utilisateur, reprendre l'identité lue |
| CORRECTION_REQUIRED | Une étape est à refaire | Dire quoi refaire d'après le code de motif, puis reprendre dans un nouveau dossier |
| REJECTED | Refusé | Fin de parcours |
| CANCELLED | Vérification annulée par un opérateur | Le parcours est remis à zéro, le client peut recommencer |
Traitez un statut inconnu sans échouer. Cette liste peut s'allonger : une application qui n'en connaît que sept casserait le jour où un huitième arrive. Tout ce qui n'est ni APPROVED ni une demande de correction se traite comme une attente.
Reprendre après une correction
Un dossier = une tentative. Il se vérifie une seule fois, et son statut ne bouge plus ensuite — sauf décision d'un opérateur.
Pour faire reprendre quelqu'un après un CORRECTION_REQUIRED, ouvrez un nouveau dossier avec le même user_id, redéposez les pièces et soumettez-le. C'est le user_id qui relie les tentatives entre elles, pas le case_id.
POST /kyc/cases { "user_id": "4312" } ← un id neuf
POST /kyc/cases/{nouveau}/document?kind=… ← toutes les pièces
POST /kyc/cases/{nouveau}/submit
Ne redéposez pas la pièce corrigée dans l'ancien dossier. Rien ne l'y relira : la vérification ne repasse jamais sur un dossier déjà vérifié. Les deux appels vous répondent 409 en le disant, plutôt que d'accepter en silence une correction qui n'arriverait nulle part.
Redéposez toutes les pièces, pas seulement celle que correction_reason désigne : le nouveau dossier repart de zéro, et une pièce manquante ferait échouer le contrôle correspondant.
Au bout de trois corrections, le dossier part en revue humaine plutôt que d'en demander une quatrième. C'est le décompte des tentatives précédentes du même user_id qui le déclenche — raison de plus pour le garder identique d'une reprise à l'autre.
Le détail des contrôles : normalized_result
GET /kyc/cases/{id} renvoie aussi ce que les contrôles automatiques ont conclu. Vous n'en avez pas besoin pour le parcours — status et correction_reason suffisent — mais c'est ce qui permet à votre propre back-office de montrer à vos opérateurs pourquoi un dossier attend, sans ouvrir celui d'AYAEL.
| Contrôle | Ce qu'il regarde | Valeurs |
|---|---|---|
duplicate | la même personne, ou la même identité, est-elle déjà passée ailleurs | NO_MATCH · SAME_PERSON · POSSIBLE_DUPLICATE · IDENTITY_CONFLICT · FACE_REUSED |
ocr | la pièce a-t-elle pu être lue | SUCCESS · ERROR |
document | ce qui est lu concorde-t-il avec ce qui est déclaré | PASS · REVIEW · FAIL |
age | la personne est-elle majeure | PASS · MINEUR_PIECE · MINEUR_DECLARE · INCONNU |
face_match | le selfie correspond-il à la photo de la pièce | PASS · REVIEW · FAIL |
support | est-ce la pièce, ou une photo d'écran / photocopie | PASS · REVIEW · FAIL |
liveness | le mouvement demandé a-t-il eu lieu | PASS · REVIEW · FAIL — absent sans défi |
PASS veut dire « vérifié », jamais « le contrôle a répondu ». Un moteur indisponible rend REVIEW, jamais PASS : ne traitez pas « différent de FAIL » comme un succès.
La liste s'allonge — support et age sont arrivés après les autres. Ignorez ce que vous ne connaissez pas plutôt que d'échouer dessus.
Ne rejouez pas la décision. Ces valeurs expliquent un statut, elles ne le remplacent pas : c'est status qui fait foi, et lui seul.
Codes de correction
AYAEL envoie un code, jamais une phrase : SELFIE_UNREADABLE, DOCUMENT_UNREADABLE, LIVENESS_FAILED, DATA_MISMATCH, FACE_MATCH_UNCERTAIN, DOCUMENT_NOT_ORIGINAL.
DOCUMENT_NOT_ORIGINAL signale qu'une reproduction a été photographiée — l'écran d'un téléphone affichant la pièce, ou une photocopie — et non le document lui-même. Demandez à l'utilisateur de photographier la pièce physique.
La formulation montrée à l'utilisateur vous appartient — vous seul savez vous adresser à lui, dans sa langue et avec vos mots. Prévoyez un message par code, et un message de repli pour un code que vous ne connaîtriez pas encore.
Le champ sandbox
Présent partout où AYAEL rend un dossier. Vrai quand il vient d'une application de mise au point.
Un dossier d'essai n'a été comparé qu'à d'autres essais : la recherche de doublons n'a rien couvert le concernant. Deux façons correctes d'en tenir compte :
Refuser la vérification par code — le plus sûr, si vous n'avez pas besoin d'aller au bout de votre parcours pendant les essais :
if (dossier.sandbox) return; // aucun compte n'est vérifié par un essai
L'accepter, mais l'enregistrer — si vous devez éprouver tout votre parcours en conditions réelles, un achat derrière la vérification par exemple. Gardez le champ avec le compte, et remettez ces comptes à « non vérifié » le jour de la bascule.
Dans les deux cas, c'est ce champ qu'il faut lire, pas le suffixe du source_app : -sandbox n'est qu'une convention de nommage.
L'identité lue sur la pièce
Présente uniquement sur un dossier approuvé — dans le webhook, dans GET /kyc/cases/{id} et dans la liste GET /kyc/cases, sous verified_identity. Brute, en majuscules.
Reprenez-la à la place du nom saisi à l'inscription. Sans cela vous gardez indéfiniment ce que l'utilisateur avait tapé. Le cas s'est produit : un client corrigeait son prénom à chaque tentative, la vérification passait avec la valeur corrigée, et le formulaire suivant repartait de l'ancienne.
La mise en forme vous appartient également : AYAEL sert plusieurs applications et n'a pas à décider comment chacune affiche un nom.
Et avant l'approbation : declared_data
Sur un dossier qui n'est pas approuvé, verified_identity vaut null — une lecture de pièce n'atteste rien tant que le dossier n'est pas tranché. Pour savoir de qui il s'agit, lisez declared_data : ce que votre utilisateur avait saisi, tel que vous nous l'avez transmis. Rendu quel que soit le statut, partout où AYAEL rend un dossier.
C'est votre donnée : nous vous la rendons pour que votre back-office affiche une file d'attente lisible sans garder sa propre copie des identités.
Ne la confondez pas avec verified_identity. declared_data n'est vérifié par rien : c'est une saisie. Pour tout ce qui engage — un registre, un contrôle réglementaire — c'est l'identité lue sur la pièce qui fait foi.
Webhook
À chaque changement de statut, AYAEL envoie un POST à l'URL déclarée, avec un en-tête X-Ayael-Signature — HMAC-SHA256 du corps exact, en hexadécimal, avec votre secret de signature.
{
"event": "case.status_changed",
"case_id": "2f6c…",
"user_id": "l-identifiant-chez-vous",
"status": "APPROVED",
"source_app": "recharge",
"sandbox": false,
"correction_reason": null,
"verified_identity": { "first_name": "AYA", "last_name": "KOUASSI", "dob": "1992-04-17" }
}
Vérifier la signature
Signez les octets reçus, jamais un objet re-sérialisé : votre encodeur JSON ne produira pas les mêmes octets que l'émetteur — espaces, ordre des clés — et la vérification échouera pour de mauvaises raisons. Comparez en temps constant, jamais avec == : une comparaison qui s'arrête au premier octet différent laisse deviner la signature attendue, essai après essai.
$corps = file_get_contents('php://input');
$recue = $_SERVER['HTTP_X_AYAEL_SIGNATURE'] ?? '';
$attendue = hash_hmac('sha256', $corps, $secret);
if (!hash_equals($attendue, $recue)) {
http_response_code(401);
exit;
}
const corps = await req.text();
const recue = req.headers.get("X-Ayael-Signature") ?? "";
const attendue = await signatureHex(secret, corps);
if (!memeSignature(attendue, recue)) {
return new Response("signature invalide", { status: 401 });
}
AYAEL réessaie — et cela vous impose deux règles. Un envoi qui échoue est rejoué de plus en plus espacé : 1 min, 5 min, 30 min, 2 h, 12 h. Au-delà, l'échec reste visible dans le back-office, où un opérateur peut le renvoyer à la main.
1. Répondez juste. AYAEL décide de rejouer d'après votre code HTTP : 2xx signifie « reçu et traité », et il ne rejouera pas ; 4xx, 5xx ou l'absence de réponse signifient « perdu ». Ne posez donc jamais un 200 en tête de script « pour être tranquille » : une signature invalide ou une panne de votre base doivent répondre 4xx ou 5xx, sinon la notification disparaît pour de bon sans que personne ne le sache. Un message bien reçu mais qui n'a rien à changer chez vous — statut que vous n'exploitez pas, utilisateur inconnu — reste un 200 légitime.
2. Soyez idempotent. Le même statut peut vous parvenir plusieurs fois : reprise, renvoi manuel, ou votre propre rattrapage. Conservez sur le compte de l'utilisateur le dossier, le statut et le motif de correction de la dernière notification traitée, et ignorez celle qui répète les trois à l'identique. Les trois ensemble : une correction dont seule la raison change porte le même statut et doit, elle, être traitée.
Gardez tout de même un filet : un balayage périodique de vos dossiers restés SUBMITTED, via GET /kyc/cases?status=SUBMITTED, pour le cas où AYAEL aurait épuisé ses reprises. Et traitez le webhook vite, en répondant tout de suite : le travail lourd vient après la réponse.
Les pièces du dossier
Montrer à vos administrateurs la pièce et le selfie déposés, sans en garder une copie chez vous. Ouvert sur vos propres dossiers : ce sont les images que vous avez déposées.
L'inventaire : par pièce, son id, son kind et sa date de dépôt — sans les images. Une pièce redéposée figure plusieurs fois ; la plus récente est celle qu'ont lue les contrôles.
L'image telle qu'elle a été reçue, servie en Cache-Control: private, no-store.
Rien à demander depuis le 24/09/2026. Le dossier d'une autre application reste un 404, comme partout ailleurs ; un 403 signifie que l'accès a été retiré à votre application, ce qui n'arrive qu'en cas d'abus constaté.
Ce que cet accès vous engage à faire. Il vous est donné pour que vous n'ayez pas à garder de copie. Une pièce d'identité qui sort d'AYAEL KYC entre dans un système dont il ne tient ni les comptes ni les journaux. Trois conséquences :
1. N'appelez la seconde route que lorsqu'un opérateur regarde — pas au chargement d'une liste, pas à chaque rafraîchissement. AYAEL journalise chaque lecture pour répondre, six mois plus tard, à « qui a vu ma pièce d'identité ? » ; noyée sous les lectures automatiques d'une interface qui précharge, cette trace ne répond plus à rien.
2. Ne stockez pas l'image chez vous. Relayez-la par votre serveur sans l'écrire : c'est précisément ce que cette route évite d'avoir à faire. Une copie de plus est une fuite possible de plus, et c'est vous qui en répondez.
3. Tenez votre propre journal. AYAEL sait que recharge a regardé, pas lequel de vos administrateurs. Cette moitié-là est chez vous.
Ce que les applications partagent
La recherche de doublons est volontairement globale : un même individu passant d'un produit à l'autre est précisément ce qu'on veut voir.
Un dossier ouvert chez vous est donc comparé à ceux de toutes les applications servies, et réciproquement. Ce qui reste cloisonné : vous ne lisez jamais les dossiers d'une autre application. Un rapprochement signale qu'un dossier jumeau existe ; son contenu n'est visible que dans le back-office, par un opérateur.
Un doublon ne rejette jamais tout seul : il envoie le dossier à un humain. Depuis le 01/10/2026 c'est le visage qui qualifie le rapprochement : SAME_PERSON (même identité, même visage, jumeau déjà approuvé) laisse l'approbation automatique s'appliquer — une personne présente sur deux plateformes ne déclenche plus de revue à chaque fois ; IDENTITY_CONFLICT (même identité, visage différent) et FACE_REUSED (même visage, autre identité) la déclenchent au contraire, et sont des signaux que le nom seul ne voyait pas.
À savoir : un user_id n'a de sens que dans l'application qui l'a émis. Le vôtre peut coïncider avec celui d'une autre sans désigner la même personne — AYAEL en tient compte, vous n'avez rien à faire de particulier.
Essayer sans rien salir
Demandez votre bac à sable, pas un bac à sable commun : il porte un identifiant propre — recharge-sandbox, mma-sandbox — et sa propre clé.
Pourquoi pas un bac à sable partagé. La clé serait la même pour tout le monde, donc impossible à renouveler sans couper les autres intégrateurs ; et le cloisonnement se faisant par application, chacun verrait les dossiers d'essai des autres — des pièces d'identité et des selfies, souvent ceux des développeurs eux-mêmes.
Ce qu'un bac à sable vous garantit. Ses dossiers ne sont comparés qu'à ceux des autres bacs à sable. Une identité inventée pour une mise au point ne peut donc pas faire remonter POSSIBLE_DUPLICATE sur un client réel et l'envoyer en revue humaine ; et vous ne découvrirez pas, en essayant avec votre propre pièce, qu'elle existe déjà chez une autre application. Deux dossiers d'essai portant la même identité se voient en revanche l'un l'autre : la détection reste éprouvable.
Un dossier d'essai consomme de l'OCR, et vos images sont stockées le temps qu'il vit. Utilisez tout de même des identités fictives : une pièce réelle déposée pour un test reste, pendant deux jours, une pièce réelle stockée.
Vider votre bac à sable
Vos dossiers d'essai s'effacent seuls, passé 48 heures sans changement — images comprises. Le balayage est quotidien : la suppression intervient donc entre 48 et 72 heures. Les dossiers de production ne sont jamais concernés.
Vide votre bac à sable immédiatement, sans attendre le délai : tous vos dossiers d'essai et leurs images, avec le compte de ce qui est parti. Avec une clé de production, répond 403 et n'efface jamais rien. Faites-le avant de passer en production, pendant que votre clé d'essai est en place.
Passer en production
Le bac à sable ne se transforme pas : vous recevez une seconde application, qui vit à côté de la première. Vous gardez ainsi un endroit où essayer la version suivante de votre intégration sans toucher à vos clients.
| Quatre gestes, dans cet ordre | |
|---|---|
| 1. Demandez l'application de production | recharge à côté de recharge-sandbox, avec son URL de webhook. Vos dossiers d'essai ne suivent pas, ni vos comptes vérifiés. |
| 2. Videz votre bac à sable, des deux côtés | Chez AYAEL-KYC : DELETE /kyc/sandbox/cases, tant que votre clé d'essai est encore en place — avec la clé de production, la route répond 403. Chez vous : effacez vos dossiers d'essai et remettez à « non vérifié » les comptes qui ne devaient leur vérification qu'à un essai. Les deux : un compte qui reste vérifié sans dossier derrière est pire qu'un compte à revérifier. |
| 3. Posez la nouvelle clé et le nouveau secret, et basculez | En un seul geste. Dans l'autre ordre, un client qui se vérifie entre les deux ouvre un dossier d'essai alors que vous vous croyez en production. |
| 4. Vérifiez | Sur un premier dossier réel, que le source_app du webhook est bien le nouveau. |
Un essai qui se glisserait entre les gestes 2 et 3 n'est pas perdu de vue : AYAEL-KYC l'efface de lui-même après 48 heures, et chez vous, refaites la remise à zéro juste après la bascule.
Pourquoi le geste 2 n'est pas négociable. Un dossier de bac à sable n'a été comparé qu'à d'autres essais : la recherche de doublons — une même identité derrière plusieurs comptes — n'a rien couvert le concernant. Laisser ce client passer pour vérifié en production, c'est inscrire dans vos registres un contrôle qui n'a pas eu lieu. AYAEL ne peut ni le faire à votre place, ni vérifier que vous l'avez fait : le niveau de vérification d'un compte vit chez vous, AYAEL ne connaît que des dossiers.
La liste de ce qu'il faut reprendre, avec votre clé de bac à sable : GET /kyc/cases?status=APPROVED — le user_id de chaque dossier est le vôtre. Gardez une trace de la raison : six mois plus tard, un client qui demande pourquoi il a dû se vérifier deux fois doit trouver une réponse. Plus simple encore si vous y pensez au départ : enregistrez le source_app de chaque dossier chez vous, il est dans la réponse comme dans le webhook.
Erreurs
| Code | Sens |
|---|---|
401 | Clé absente, inconnue, ou application suspendue |
404 | Dossier inexistant ou appartenant à une autre application |
403 | La route existe, votre application n'y a pas droit — aujourd'hui, la relecture des pièces |
400 | Contenu refusé : code de correction inconnu, décision invalide |
409 | Le dossier a déjà été vérifié : il ne sera plus relu. Reprenez dans un nouveau dossier — voir « Reprendre après une correction » |
503 | Vérification non planifiée. Le dossier repasse IN_PROGRESS, réessayez |
Le 404 sur le dossier d'autrui est délibéré : un 403 confirmerait son existence à qui l'interroge au hasard. Le 403, lui, ne porte jamais que sur vos dossiers — c'est pourquoi il peut dire la vérité sans rien révéler.
Collection Postman
Le parcours complet, les filtres de suivi, la décision manuelle et les refus attendus. Copiez, puis dans Postman : Import → Raw text.
Chargement…
Renseignez ensuite base_url et api_key dans l'environnement. La requête « Créer le dossier » enregistre elle-même le case_id pour les suivantes.
Le fichier est aussi téléchargeable : ayael-kyc.postman_collection.json, et la description OpenAPI se trouve sur api.ayaelkyc.com/openapi.json.