> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Step-up : créer une étape | API Didit Authentication

> Didit : POST /step-up/ applique vos règles step-up avant une action sensible et renvoie required false, ou une URL de vérification pour la personne.

> [!WARNING]
>
> L'authentification est nouvelle. Ces endpoints sont servis par l'émetteur de votre application, par exemple `https://<name>.auth.didit.me`, et n'acceptent jamais votre clé API Didit.

Les paramètres de requête `step_up` et `status` présents dans votre `return_to` indiquent seulement à votre application de vérifier. Avant d'agir, lisez le step-up avec [Obtenir un step-up](https://docs.didit.me/fr/authentication/api-reference/retrieve-step-up.md) et ne poursuivez que si `status` vaut `approved`.


## OpenAPI

````yaml fr/openapi-authentication.json POST /step-up/
openapi: 3.0.3
info:
  title: Didit Authentication API
  version: 1.0.0
  description: >-
    Les endpoints côté serveur de Didit Authentication, le produit de connexion
    pour les utilisateurs finaux de votre application. Authentication est
    **nouveau** et arrive en production dans la version 2026-11-01.


    ## URL de base


    Chaque application constitue son propre émetteur OpenID Connect, et chaque
    endpoint ci-dessous est servi à la racine de cet émetteur :


    - `https://<name>.auth.didit.me` pour une application en production, ou
    `https://<name>.sandbox.auth.didit.me` pour une application en sandbox.

    - Votre propre domaine personnalisé, une fois actif. Il remplace alors
    l'hôte par défaut en tant qu'émetteur : les nouveaux tokens l'indiquent dans
    `iss`, la découverte pointe vers lui et la clé publiable change. Les tokens
    d'accès émis sous l'hôte par défaut échouent sur `/session/`, `/step-up/` et
    `/monitoring-token/` tant qu'ils ne sont pas rafraîchis.


    La production et le sandbox sont des applications distinctes ; ce sont donc
    des émetteurs distincts disposant de clés distinctes. La clé publiable est
    `pk_live_` (production) ou `pk_test_` (sandbox), suivie de l'encodage
    base64url sans remplissage de l'émetteur, sans barre oblique finale :
    décodez-la pour retrouver l'émetteur. Il s'agit d'un identifiant public, pas
    d'un identifiant d'authentification.


    Les mêmes endpoints JSON sont également servis sous
    `https://apx.didit.me/auth/v2/authentication/{application_id}/`. Les tokens
    indiquent toujours l'émetteur ci-dessus, quelle que soit l'adresse qui les a
    émis.


    ## Identifiants


    Ces endpoints n'acceptent jamais votre clé API Didit ni un token de console.


    - La découverte, le JWKS et `POST /token/` ne prennent aucun identifiant.
    Les clients sont publics : il n'y a pas de secret client, et un code
    d'autorisation est lié à son vérificateur PKCE (`S256`). `client_id` est
    l'identifiant de votre application.

    - `/session/`, `/step-up/` et `/monitoring-token/` prennent le token d'accès
    de la personne : `Authorization: Bearer <access_token>`. Chaque appel réussi
    compte comme une activité sur la session, ce qui réinitialise le délai
    d'inactivité de la session.


    ## Corps de requête


    Chaque endpoint acceptant un corps prend en charge les corps au format JSON,
    au format formulaire et multipart. Ce document liste le format JSON, ainsi
    que le format encodé en formulaire pour `POST /token/` tel que les clients
    OAuth l'envoient. Un champ que l'endpoint n'accepte pas renvoie `400
    {"<field>": "unknown_field"}`.


    ## Tokens


    - **Token d'accès** : un JWT ES256 avec l'en-tête `typ: at+jwt`, valide
    pendant au maximum 5 minutes et jamais plus longtemps que sa session.
    Revendications : `AccessTokenClaims`.

    - **Token d'identification** : un JWT ES256 avec l'en-tête `typ: JWT`.
    Revendications : `IDTokenClaims`. Il n'est jamais accepté en tant que token
    d'accès.

    - **Token d'actualisation** : une chaîne aléatoire renouvelée à chaque
    utilisation. Envoyer à nouveau un token d'actualisation déjà utilisé révoque
    la session.


    Vérifiez un token à l'aide des clés provenant de `GET
    /.well-known/jwks.json` : l'algorithme doit être `ES256`, `typ` doit
    correspondre au type de token, `iss` doit être égal à l'émetteur configuré
    et `aud` à l'identifiant de votre application. La vérification hors ligne ne
    détecte une session révoquée qu'une fois son token d'accès expiré ; `GET
    /session/` constitue la vérification instantanée.


    ## Erreurs


    | Corps | Quand |

    | --- | --- |

    | `{"error": "<code>"}` | Erreurs de `POST /token/`, toutes avec le statut
    `400`, au format de corps de la [section 5.2 de la RFC
    6749](https://www.rfc-editor.org/rfc/rfc6749#section-5.2) : `invalid_grant`,
    `invalid_client` et le `mfa_required` de Didit. Erreurs de service :
    `temporarily_unavailable`, `monitoring_unavailable`, `monitoring_disabled`.
    |

    | `{"<field>": ["<message>"]}` ou `{"<field>": "<code>"}` | La validation
    d'un champ de la requête a échoué. `non_field_errors` indique une règle
    s'appliquant à plusieurs champs, et `unknown_field` un champ que l'endpoint
    n'accepte pas. |

    | `{"detail": "<message>"}` | `401` (`invalid_token`), `403`
    (`origin_not_allowed`), `404` (application inconnue ou step-up inconnu) et
    `429` (limite de débit). Également `400` pour un corps impossible à analyser
    (`JSON parse error - ...`), `405` pour une méthode que l'endpoint ne prend
    pas en charge et `415` pour un `Content-Type` non pris en charge. |


    Une réponse `401` comporte également `WWW-Authenticate: Bearer`, et une
    réponse `429` comporte `Retry-After` en secondes.


    Deux refus ne sont pas au format JSON. Une adresse qui n'est pas un émetteur
    est refusée avant d'atteindre ces endpoints, par exemple avec une erreur
    `404` vide pour un nom `*.auth.didit.me` non utilisé. Un `step_up_id` qui
    n'est pas un UUID renvoie une page `404`.


    ## Limites de débit


    Une requête doit franchir deux couches.


    **Limites par endpoint**, comptabilisées par adresse IP cliente, par
    application et par endpoint :


    - Découverte et JWKS : 1 200 requêtes par minute et 12 000 par heure.

    - Tous les autres endpoints : 30 requêtes par minute et 300 par heure. `GET`
    et `DELETE /session/` partagent le même décompte.


    En cas de dépassement de la limite : `429 {"detail": "Too many requests from
    this IP address. Please wait before trying again. Expected available in 60
    seconds."}`, ou `3600 seconds` pour la limite horaire.


    **Protection contre les bots**, sur chaque `POST` et `DELETE`. Ses
    compartiments sont partagés par toutes les requêtes `POST` et `DELETE` de
    l'application, y compris les étapes de connexion hébergées, et se
    remplissent en continu :


    - 60 requêtes par minute par adresse IP cliente.

    - 30 requêtes par minute par appareil, pour les requêtes qui identifient
    leur appareil, comme le font les SDK natifs et de navigateur.

    - 3 000 requêtes par minute pour l'ensemble de l'application, toutes
    adresses IP confondues. Les rafraîchissements de token (`POST /token/` avec
    `grant_type=refresh_token`) utilisent plutôt leur propre compartiment : 6
    000 par minute pour l'ensemble de l'application.


    En cas de dépassement d'un compartiment : `429 {"detail": "rate_limited
    Expected available in N seconds."}` (`1 second.` lorsque N vaut 1). Le
    compartiment par IP s'étend sur l'ensemble des endpoints ; ainsi, une
    adresse unique qui combine des appels de token, de step-up et de déconnexion
    peut le vider avant même d'atteindre la moindre limite par endpoint. Le
    compartiment applicatif prend en compte chaque client de l'application, y
    compris les navigateurs.


    Les deux couches répondent avant l'exécution de l'endpoint : une requête
    ayant reçu une réponse `429` n'a donc pas été exécutée. Réessayez-la après
    le délai indiqué par `Retry-After`, y compris pour un rafraîchissement.


    ## Requêtes de navigateur


    Une requête comportant un en-tête `Origin` doit provenir de l'une des
    origines propres à l'application ou de ses origines autorisées, sous peine
    de recevoir une erreur `403 origin_not_allowed`. Les requêtes de serveur à
    serveur n'envoient aucun en-tête `Origin`.


    ## Non traité ici


    Ces routes desservent les pages hébergées, les SDK natifs et de navigateur,
    ainsi que les agents IA. Elles ne font pas partie du contrat serveur, et les
    SDK serveur ne les appellent pas :


    - Étapes de connexion : `POST /authorize/`, `POST /password/`, `/email/*`,
    `/phone/*`, `/password-reset/*`, `/passkeys/*`, `/mfa/*`, `/social/*` et
    `/protection/complete/`.

    - Pages hébergées et rappels : `/sign-in`, `/magic`, `/invite`, `/reset`,
    `/verify/`, `/sso/*`, `/wallet/callback/`, `/logo/` et les fichiers
    d'association d'applications natives sous `/.well-known/`.

    - Accès pour les agents : `/agents/*` et `/grants/`, y compris `GET
    /agents/session/`, la vérification en direct du token d'accès d'un agent IA.
    L'accès des agents ne fait pas partie de cette version du contrat.

    - L'API de gestion de la console pour Authentication, qui requiert des
    sessions de console.


    Les événements d'Authentication ne sont pas encore transmis sous forme de
    webhooks ; ce document ne définit donc aucun webhook.
servers:
  - url: https://{issuer}
    description: 'L''émetteur de votre application : son origine de connexion.'
    variables:
      issuer:
        default: your-app.auth.didit.me
        description: >-
          L'hôte de l'émetteur : `<name>.auth.didit.me` (production),
          `<name>.sandbox.auth.didit.me` (sandbox) ou votre domaine personnalisé
          actif.
security: []
tags:
  - name: Issuer
    description: Clés de découverte et de signature, pour vérifier les tokens.
  - name: Tokens
    description: Les autorisations par code d'autorisation et de renouvellement.
  - name: Sessions
    description: Vérifier et terminer la session associée à un token d'accès.
  - name: Step-ups
    description: Vérification avant une action sensible.
  - name: Monitoring
    description: >-
      Informations d'authentification de l'appareil pour les événements de
      surveillance des transactions.
paths:
  /step-up/:
    post:
      tags:
        - Step-ups
      summary: Créer un step-up
      description: >-
        Demande si la personne doit effectuer une vérification avant une action
        sensible, en utilisant les règles d'authentification renforcée (step-up)
        que vous avez définies pour l'application.


        - `200 {"required": false}` : aucune règle ne s'applique, poursuivez.

        - `201` : redirigez la personne vers `verification_url`. Elle revient
        vers `return_to` avec `step_up=<id>&status=approved`, mais cette requête
        ne prouve rien : lisez le step-up avec `GET /step-up/{step_up_id}/` et
        agissez uniquement sur `approved`.


        Un step-up n'échoue jamais de manière permissive (fail-open) : lorsque
        la vérification est indisponible, l'action reste bloquée.
      operationId: authentication_create_step_up
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StepUpRequest'
            examples:
              Payout:
                summary: Avant un versement
                value:
                  action: payout
                  amount: '900.00'
                  currency: USD
                  return_to: https://app.example.com/payouts/confirm
              Bank:
                summary: Avant de modifier un compte bancaire
                value:
                  action: bank
                  return_to: https://app.example.com/settings/bank
      responses:
        '200':
          description: Aucune règle ne s'applique.
          headers:
            Cache-Control:
              description: 'Toujours `no-store` : ne jamais mettre cette réponse en cache.'
              schema:
                type: string
                example: no-store
            Pragma:
              description: Toujours `no-cache`.
              schema:
                type: string
                example: no-cache
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StepUpNotRequired'
              examples:
                NotRequired:
                  summary: Poursuivre
                  value:
                    required: false
        '201':
          description: La personne doit d'abord être vérifiée.
          headers:
            Cache-Control:
              description: 'Toujours `no-store` : ne jamais mettre cette réponse en cache.'
              schema:
                type: string
                example: no-store
            Pragma:
              description: Toujours `no-cache`.
              schema:
                type: string
                example: no-cache
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StepUpRequired'
              examples:
                Required:
                  summary: Un selfie avant un paiement
                  value:
                    id: 94efc2c3-ca84-42fe-900b-6fb61669a7a1
                    rule: payout
                    method: selfie
                    status: pending
                    level: null
                    amount: '900.00'
                    currency: USD
                    created_at: '2026-10-17T10:41:14.894632Z'
                    completed_at: null
                    expires_at: '2026-10-17T11:11:14.894581Z'
                    required: true
                    verification_url: >-
                      https://your-app.auth.didit.me/verify/?verification=1nSX7HJCXAP9XPA9AeJfQxIuPk2GkG0dGSOzCA2Zpww
                    expires_in: 1800
        '400':
          description: >-
            Un champ est manquant ou non valide. `return_to:
            invalid_redirect_uri` signifie qu'une règle s'applique et que
            `return_to` ne fait pas partie des URI de redirection de
            l'application.
          headers:
            Cache-Control:
              description: 'Toujours `no-store` : ne jamais mettre cette réponse en cache.'
              schema:
                type: string
                example: no-store
            Pragma:
              description: Toujours `no-cache`.
              schema:
                type: string
                example: no-cache
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FieldErrors'
              examples:
                MissingAmount:
                  summary: Un paiement sans montant
                  value:
                    amount:
                      - required
                InvalidReturnTo:
                  summary: return_to non enregistré
                  value:
                    return_to: invalid_redirect_uri
                UnknownAction:
                  summary: Action non prise en charge
                  value:
                    action:
                      - '"withdraw" is not a valid choice.'
        '401':
          $ref: '#/components/responses/InvalidToken'
        '403':
          $ref: '#/components/responses/OriginNotAllowed'
        '404':
          $ref: '#/components/responses/ApplicationNotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/TemporarilyUnavailable'
      security:
        - AccessToken: []
components:
  schemas:
    StepUpRequest:
      type: object
      description: L'action sensible que la personne est sur le point d'effectuer.
      required:
        - action
        - return_to
      additionalProperties: false
      properties:
        action:
          type: string
          enum:
            - payout
            - bank
            - contact
          description: >-
            `payout` (un retrait ou un virement), `bank` (l'ajout ou la
            modification d'un compte bancaire) ou `contact` (la modification
            d'une adresse e-mail ou d'un numéro de téléphone). Un retrait ou un
            virement applique également la règle du nouvel appareil.
        amount:
          type: string
          pattern: ^[0-9]{1,16}(\.[0-9]{1,2})?$
          description: >-
            Requis pour `payout` : le montant, jusqu'à deux décimales. Un nombre
            JSON est également accepté.
          example: '900.00'
        currency:
          type: string
          pattern: ^[A-Z]{3}$
          description: >-
            Code ISO 4217 de `amount`. La valeur par défaut est la devise de la
            règle de versement ; un versement dans une autre devise est toujours
            vérifié.
          example: USD
        return_to:
          type: string
          maxLength: 2048
          description: >-
            Emplacement où la personne est redirigée après la vérification, avec
            `step_up=<id>&status=approved` ajouté. Lorsqu'une règle s'applique,
            il doit s'agir de l'un des URI de redirection de l'application ; si
            aucune règle ne s'applique, il n'est pas vérifié.
          example: https://app.example.com/payouts/confirm
    StepUpNotRequired:
      type: object
      description: 'Aucune règle ne s''applique : poursuivez l''action.'
      required:
        - required
      properties:
        required:
          type: boolean
          enum:
            - false
    StepUpRequired:
      description: >-
        Une nouvelle étape de vérification renforcée. Redirigez la personne vers
        `verification_url`.
      allOf:
        - $ref: '#/components/schemas/StepUp'
        - type: object
          required:
            - required
            - verification_url
            - expires_in
          properties:
            required:
              type: boolean
              enum:
                - true
            verification_url:
              type: string
              format: uri
              description: >-
                La page hébergée où la personne se vérifie ; ouvrez-la dans le
                navigateur de la personne. Elle fonctionne jusqu'à ce que
                l'authentification renforcée soit approuvée ou expire, afin
                qu'une personne refusée puisse réessayer depuis celle-ci.
              example: >-
                https://your-app.auth.didit.me/verify/?verification=1nSX7HJCXAP9XPA9AeJfQxIuPk2GkG0dGSOzCA2Zpww
            expires_in:
              type: integer
              description: >-
                Secondes pendant lesquelles l'étape de renforcement reste
                ouverte : 1800.
              example: 1800
    FieldErrors:
      title: Field errors
      type: object
      description: >-
        Erreurs de validation par champ. Une valeur est une liste de messages ou
        un code unique (`unknown_field`, `invalid_redirect_uri`).
        `non_field_errors` contient les règles transversales aux champs.
      additionalProperties:
        oneOf:
          - type: string
          - type: array
            items:
              type: string
    StepUp:
      type: object
      description: Une vérification que la personne doit réussir avant l'action.
      required:
        - id
        - rule
        - method
        - status
        - level
        - amount
        - currency
        - created_at
        - completed_at
        - expires_at
      properties:
        id:
          type: string
          format: uuid
          example: 94efc2c3-ca84-42fe-900b-6fb61669a7a1
        rule:
          type: string
          enum:
            - payout
            - device
            - bank
            - contact
          description: >-
            La règle qui l'a demandée. Lorsque plusieurs s'appliquent, celle
            avec la méthode la plus forte.
          example: payout
        method:
          type: string
          enum:
            - liveness
            - selfie
            - full_id
          description: >-
            Ce que la personne effectue : `liveness`, `selfie` (détection du
            vivant mise en correspondance avec le visage que Didit possède déjà)
            ou `full_id` (document et selfie). Lorsque Didit ne détient pas
            encore de visage pour la personne, `selfie` devient `full_id` dès
            qu'elle ouvre l'adresse `verification_url`.
          example: selfie
        status:
          type: string
          enum:
            - pending
            - review
            - approved
            - declined
            - expired
            - superseded
          description: >-
            N'agissez que sur `approved`. `review` attend l'examen de votre
            équipe. `declined` n'est pas définitif tant que `verification_url`
            fonctionne : la personne peut réessayer, et la vérification
            renforcée repasse à `pending`. `superseded` signifie qu'une autre
            vérification a pris le relais, ou que le résultat ne concernait pas
            cette vérification renforcée : créez-en une nouvelle.
          example: pending
        level:
          type: string
          nullable: true
          enum:
            - liveness
            - liveness_face_match
            - full_id
            - null
          description: Le niveau atteint par la vérification, une fois approuvée.
          example: null
        amount:
          type: string
          nullable: true
          description: Le montant du paiement, deux décimales.
          example: '900.00'
        currency:
          type: string
          nullable: true
          example: USD
        created_at:
          type: string
          format: date-time
          example: '2026-10-17T10:41:14.894632Z'
        completed_at:
          type: string
          format: date-time
          nullable: true
          example: null
        expires_at:
          type: string
          format: date-time
          description: >-
            30 minutes après la création. La lecture d'un step-up collecte
            d'abord tout résultat atteint par sa vérification, de sorte qu'il
            peut toujours afficher `approved`, `review` ou `declined` après ce
            délai ; un step-up encore `pending` affiche `expired`.
          example: '2026-10-17T11:11:14.894581Z'
    Detail:
      type: object
      required:
        - detail
      properties:
        detail:
          type: string
          example: invalid_token
    OAuthError:
      title: OAuth error
      type: object
      description: Une erreur OAuth (RFC 6749 section 5.2) ou une erreur de service.
      required:
        - error
      properties:
        error:
          type: string
          example: invalid_grant
  responses:
    InvalidToken:
      description: >-
        Le token d'accès est manquant, malformé, expiré, signé par un autre
        émetteur ou une autre clé, est un token d'ID, un token d'agent, ou a été
        remplacé par un rafraîchissement ; ou sa session a été révoquée, a
        expiré pour cause d'inactivité, a dépassé sa durée de vie ou ne respecte
        plus votre politique MFA, ou la personne a été bloquée ou son e-mail
        n'est plus vérifié. Renvoyez la personne vers la connexion.
      headers:
        WWW-Authenticate:
          description: '`Bearer`.'
          schema:
            type: string
            example: Bearer
        Cache-Control:
          description: 'Toujours `no-store` : ne jamais mettre cette réponse en cache.'
          schema:
            type: string
            example: no-store
        Pragma:
          description: Toujours `no-cache`.
          schema:
            type: string
            example: no-cache
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Detail'
          examples:
            InvalidToken:
              summary: Token d'accès rejeté
              value:
                detail: invalid_token
    OriginNotAllowed:
      description: >-
        La requête contenait un `Origin` qui n'est ni celui de l'application ni
        dans ses origines autorisées. Les requêtes serveur n'envoient pas de
        `Origin`.
      headers:
        Cache-Control:
          description: 'Toujours `no-store` : ne jamais mettre cette réponse en cache.'
          schema:
            type: string
            example: no-store
        Pragma:
          description: Toujours `no-cache`.
          schema:
            type: string
            example: no-cache
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Detail'
          examples:
            OriginNotAllowed:
              summary: Origine non autorisée
              value:
                detail: origin_not_allowed
    ApplicationNotFound:
      description: >-
        L'adresse appartient à une application, mais Authentication n'est pas
        actif pour celle-ci ; sur l'adresse `apx.didit.me`, l'identifiant de
        l'application est inconnu. Une adresse n'appartenant à aucune
        application est refusée avant d'atteindre l'endpoint, et cette réponse
        n'est pas en JSON.
      headers:
        Cache-Control:
          description: 'Toujours `no-store` : ne jamais mettre cette réponse en cache.'
          schema:
            type: string
            example: no-store
        Pragma:
          description: Toujours `no-cache`.
          schema:
            type: string
            example: no-cache
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Detail'
          examples:
            ApplicationNotFound:
              summary: Application inconnue
              value:
                detail: No Configuration matches the given query.
    RateLimited:
      description: >-
        Trop de requêtes issues de cette adresse IP pour cet endpoint, ou, sur
        un `POST` ou `DELETE`, un compartiment de protection contre les robots
        est vide. Rien n'a été exécuté : attendez `Retry-After` secondes, puis
        réessayez.
      headers:
        Retry-After:
          description: Secondes à attendre avant de réessayer.
          schema:
            type: integer
            example: 60
        Cache-Control:
          description: 'Toujours `no-store` : ne jamais mettre cette réponse en cache.'
          schema:
            type: string
            example: no-store
        Pragma:
          description: Toujours `no-cache`.
          schema:
            type: string
            example: no-cache
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Detail'
          examples:
            RateLimited:
              summary: Limite du endpoint
              value:
                detail: >-
                  Too many requests from this IP address. Please wait before
                  trying again. Expected available in 60 seconds.
            BotProtection:
              summary: Limite de protection contre les bots (POST et DELETE)
              value:
                detail: rate_limited Expected available in 2 seconds.
    TemporarilyUnavailable:
      description: >-
        Un service dont dépend la requête est temporairement indisponible.
        Réessayez en augmentant progressivement l'intervalle entre les
        tentatives.
      headers:
        Cache-Control:
          description: 'Toujours `no-store` : ne jamais mettre cette réponse en cache.'
          schema:
            type: string
            example: no-store
        Pragma:
          description: Toujours `no-cache`.
          schema:
            type: string
            example: no-cache
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/OAuthError'
          examples:
            TemporarilyUnavailable:
              summary: Réessayer plus tard
              value:
                error: temporarily_unavailable
  securitySchemes:
    AccessToken:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        Le token d'accès de la personne issu de `POST /token/` : un JWT ES256
        avec l'en-tête `typ: at+jwt`, valide pendant au plus 5 minutes. Il ne
        s'agit pas de votre clé API Didit.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.