Granite Auth API

Sign-up, login, sessions, and password reset.

GitHub OpenAPI

Operations 18

POST /v1/auth/signup Create an account #
POST /v1/auth/signup/confirm Confirm a sign-up with the emailed code and sign in #
POST /v1/auth/verify-email Confirm an email address #
POST /v1/auth/verify-email/resend Send a new verification email #
POST /v1/auth/login Sign in #
POST /v1/auth/sso/start Start single sign-on #
POST /v1/auth/sso/finish Finish single sign-on #
POST /v1/auth/refresh Get new tokens #
POST /v1/auth/logout Sign out this device #
POST /v1/auth/password-reset Email a password reset link or code #
POST /v1/auth/password-reset/confirm Set a new password #
POST /v1/auth/password-reset/code/check Check an emailed reset code #
POST /v1/auth/password-reset/code/confirm Set a new password with an emailed code and sign in #
POST /v1/auth/login/2fa Finish signing in with a second factor #
POST /v1/auth/step-up Confirm the password for sensitive actions #
POST /v1/auth/app-handoff Hand a sign-in to the desktop app #
POST /v1/auth/app-handoff/finish Redeem a sign-in handed to the app #
POST /v1/auth/oidc/{provider} Sign in with Google or Apple #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/granite-md:granite-md-auth-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

granite-md-auth-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Granite Auth API
  version: 1.0.0
  summary: REST API for Granite clients, bots, and integrations.
  description: 'Conventions (apply to every operation; see PLAN.md 3.2 and 3.6):


    - **Base URL** `https://api.granite.md`. All public operations live under `/v1`.

    After Phase 6 ships, `/v1` changes are additive only.

    - **Errors** use RFC 9457 problem details (`application/problem+json`, schema `Problem`).

    The `type` URI is `https://granite.md/problems/`; `code` refines it for programmatic handling.

    Every response carries `X-Request-ID`, also echoed as `request_id` in problems.

    - **Pagination** is cursor based. List operations accept `cursor` and `limit` and return

    `{ "items": [...], "next_cursor": "..." }`. `next_cursor` is absent on the last page.

    Cursors are opaque; clients must not parse or build them.

    - **Idempotency**: non-idempotent `POST` operations accept an `Idempotency-Key` header

    (1-255 printable ASCII). Repeating a request with the same key and body within 24 hours

    returns the original response; the same key with a different body returns `409` with

    code `idempotency_key_reused`.

    - **IDs** are UUIDv7 strings. **Timestamps** are RFC 3339 in UTC.

    - **Authentication**: `Authorization: Bearer ` with a session access token,

    a personal API token (`gr_pat_...`), or an OAuth access token. Scopes are listed per operation.

    - **Rate limits**: each personal API token may make 600 requests a minute. Over the limit

    the API answers `429` with code `token_rate_limited` and a `Retry-After` header (seconds).'
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
- url: https://api.granite.md
  description: Production
- url: http://localhost:8080
  description: Local development
security:
- bearerAuth: []
tags:
- name: Auth
  description: Sign-up, login, sessions, and password reset.
paths:
  /v1/auth/signup:
    post:
      operationId: signup
      tags:
      - Auth
      summary: Create an account
      description: 'Creates an unverified account and emails a verification link, or with `confirm: code`

        (the mobile apps) a 6-digit code to send to `POST /v1/auth/signup/confirm`. The response

        is the same whether or not the email is already registered: a verified account''s owner

        is notified instead, and an account not yet confirmed gets a new link or code (earlier

        ones stop working) whose confirmation also sets this request''s password and name.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignupRequest'
      responses:
        '202':
          $ref: '#/components/responses/Accepted'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/signup/confirm:
    post:
      operationId: confirmSignup
      tags:
      - Auth
      summary: Confirm a sign-up with the emailed code and sign in
      description: 'Confirms the email address with the 6-digit code from a `confirm: code` sign-up (or

        resend) and signs in on this device in the same step, so the password is not typed

        again. A code works once, for 15 minutes, and stops working after 5 wrong tries. A

        wrong, expired, or used-up code and an unknown email all answer 400 with code

        `invalid_signup_code`.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignupConfirmRequest'
      responses:
        '200':
          description: Email confirmed and signed in.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/verify-email:
    post:
      operationId: verifyEmail
      tags:
      - Auth
      summary: Confirm an email address
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenRequest'
      responses:
        '204':
          description: Email confirmed.
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/verify-email/resend:
    post:
      operationId: resendVerification
      tags:
      - Auth
      summary: Send a new verification email
      description: 'Sends a new link, or with `confirm: code` a new 6-digit code (earlier codes stop

        working). Always answers 202, whether or not the email has an unverified account.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailConfirmRequest'
      responses:
        '202':
          $ref: '#/components/responses/Accepted'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/login:
    post:
      operationId: login
      tags:
      - Auth
      summary: Sign in
      description: 'Checks the password and starts a session for the device. After repeated failures the

        email is locked for a while (429), whether or not it has an account.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: Signed in.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '202':
          description: Password accepted; the account needs a second factor. Continue with `POST /v1/auth/login/2fa`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MFAChallenge'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/sso/start:
    post:
      operationId: startSSO
      tags:
      - Auth
      summary: Start single sign-on
      description: 'Finds the workspace that verified the email''s domain and set up SSO, and returns the

        identity provider''s sign-in URL. Open it in the browser; the provider returns to the

        web app''s `/sso/callback`, which finishes with `POST /v1/auth/sso/finish`.


        With `client=mobile` (the phone app, which opens the page in the system browser), the

        `state` (OpenID Connect) or `handoff` code (SAML) starts with `app.`: the web callback

        then hands it to the phone app in a `granite://sso-callback` link instead of finishing

        itself, and the app finishes.'
      security: []
      parameters:
      - name: client
        in: query
        required: false
        description: Who finishes the sign-in; `mobile` for the phone app.
        schema:
          type: string
          enum:
          - web
          - mobile
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - email
              properties:
                email:
                  type: string
                  maxLength: 320
      responses:
        '200':
          description: Where to sign in.
          content:
            application/json:
              schema:
                type: object
                required:
                - authorize_url
                properties:
                  authorize_url:
                    type: string
        '429':
          $ref: '#/components/responses/TooManyRequests'
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/sso/finish:
    post:
      operationId: finishSSO
      tags:
      - Auth
      summary: Finish single sign-on
      description: 'Exchanges the code from the identity provider and starts a session. People new to the

        workspace join it with the connection''s role. Two-step verification is left to the

        identity provider.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - device
              description: The OpenID Connect `state` and `code`, or the SAML `handoff` code.
              properties:
                state:
                  type: string
                  maxLength: 200
                code:
                  type: string
                  maxLength: 4000
                handoff:
                  type: string
                  maxLength: 200
                device:
                  $ref: '#/components/schemas/DeviceInfo'
      responses:
        '200':
          description: Signed in.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/refresh:
    post:
      operationId: refreshSession
      tags:
      - Auth
      summary: Get new tokens
      description: 'Exchanges a refresh token for a new access token and a new refresh token. Each refresh

        token works once; presenting a used one ends every session of that sign-in

        (code `refresh_token_reused`). Clients must not refresh concurrently.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefreshRequest'
      responses:
        '200':
          description: New tokens.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/logout:
    post:
      operationId: logout
      tags:
      - Auth
      summary: Sign out this device
      responses:
        '204':
          description: Signed out.
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/password-reset:
    post:
      operationId: requestPasswordReset
      tags:
      - Auth
      summary: Email a password reset link or code
      description: 'Emails a reset link, or with `confirm: code` (the mobile apps) a 6-digit code for

        `POST /v1/auth/password-reset/code/confirm` (earlier codes stop working). Always

        answers 202, whether or not the email has an account.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailConfirmRequest'
      responses:
        '202':
          $ref: '#/components/responses/Accepted'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/password-reset/confirm:
    post:
      operationId: confirmPasswordReset
      tags:
      - Auth
      summary: Set a new password
      description: Sets the password from a reset link and signs out every device.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PasswordResetConfirmRequest'
      responses:
        '204':
          description: Password changed.
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/password-reset/code/check:
    post:
      operationId: checkPasswordResetCode
      tags:
      - Auth
      summary: Check an emailed reset code
      description: 'Says whether a reset code is right before the person chooses a new password. The code

        is not used up; a wrong one counts as one of its 5 tries. A wrong, expired, or used-up

        code and an unknown email all answer 400 with code `invalid_reset_code`.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailCodeRequest'
      responses:
        '204':
          description: The code is right.
        '429':
          $ref: '#/components/responses/TooManyRequests'
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/password-reset/code/confirm:
    post:
      operationId: confirmPasswordResetCode
      tags:
      - Auth
      summary: Set a new password with an emailed code and sign in
      description: 'Sets the new password with the 6-digit code from a `confirm: code` reset request, signs

        out every other session, and signs in on this device. Accounts with two-step

        verification get a challenge to finish with `POST /v1/auth/login/2fa`, as at sign-in.

        A code works once, for 15 minutes, and stops working after 5 wrong tries; failures

        answer 400 with code `invalid_reset_code`.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PasswordResetCodeConfirmRequest'
      responses:
        '200':
          description: Password changed and signed in.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '202':
          description: Password changed; the account needs a second factor. Continue with `POST /v1/auth/login/2fa`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MFAChallenge'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/login/2fa:
    post:
      operationId: loginSecondFactor
      tags:
      - Auth
      summary: Finish signing in with a second factor
      description: Send either `code` (from the authenticator app) or `recovery_code`. Each works once.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SecondFactorRequest'
      responses:
        '200':
          description: Signed in.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/step-up:
    post:
      operationId: stepUp
      tags:
      - Auth
      summary: Confirm the password for sensitive actions
      description: 'Re-checks the password (and the authenticator code when two-factor authentication is on).

        For the next 10 minutes this session may change two-factor settings. Other sensitive

        actions answer 403 with code `step_up_required` until this is done.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StepUpRequest'
      responses:
        '204':
          description: Confirmed.
        '429':
          $ref: '#/components/responses/TooManyRequests'
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/app-handoff:
    post:
      operationId: createAppHandoff
      tags:
      - Auth
      summary: Hand a sign-in to the desktop app
      description: 'For the desktop app''s sign-in through the browser (Google, single sign-on). The app opens

        the web app with a PKCE challenge; once signed in, the web app calls this and passes the

        one-time code to the app in a `granite://auth` link. The app redeems it with

        `POST /v1/auth/app-handoff/finish`. Needs a session, not an API token. A session from

        single sign-on hands the app a single sign-on session for the same workspace.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - code_challenge
              properties:
                code_challenge:
                  type: string
                  description: base64url(SHA-256(code_verifier)) without padding (RFC 7636 S256).
                  minLength: 43
                  maxLength: 43
      responses:
        '200':
          description: A one-time code, valid for two minutes.
          content:
            application/json:
              schema:
                type: object
                required:
                - code
                - expires_in
                properties:
                  code:
                    type: string
                  expires_in:
                    type: integer
                    description: Seconds the code stays valid.
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/app-handoff/finish:
    post:
      operationId: finishAppHandoff
      tags:
      - Auth
      summary: Redeem a sign-in handed to the app
      description: 'Exchanges the one-time code from `POST /v1/auth/app-handoff` and the app''s PKCE verifier

        for a session on the app''s device. A code works once, also when the verifier is wrong.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - code
              - code_verifier
              - device
              properties:
                code:
                  type: string
                  maxLength: 200
                code_verifier:
                  type: string
                  minLength: 43
                  maxLength: 128
                device:
                  $ref: '#/components/schemas/DeviceInfo'
      responses:
        '200':
          description: Signed in.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        default:
          $ref: '#/components/responses/Error'
  /v1/auth/oidc/{provider}:
    post:
      operationId: socialLogin
      tags:
      - Auth
      summary: Sign in with Google or Apple
      description: 'Send the ID token from the provider''s sign-in SDK. Before signing in, the client makes a

        random `nonce`, passes hex(SHA-256(nonce)) to the provider, and sends the raw nonce here.

        A first sign-in with a verified email links to the account with that email or creates

        one. Returns 202 when the account has two-factor authentication on.'
      security: []
      parameters:
      - name: provider
        in: path
        required: true
        schema:
          type: string
          enum:
          - google
          - apple
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SocialLoginRequest'
      responses:
        '200':
          description: Signed in.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '202':
          description: Continue with `POST /v1/auth/login/2fa`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MFAChallenge'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        default:
          $ref: '#/components/responses/Error'
components:
  headers:
    RequestID:
      description: Identifier of this request, for support and log correlation.
      schema:
        type: string
    RetryAfter:
      description: Seconds to wait before retrying.
      schema:
        type: integer
  schemas:
    TokenRequest:
      type: object
      required:
      - token
      properties:
        token:
          type: string
          maxLength: 200
    PasswordResetCodeConfirmRequest:
      type: object
      required:
      - email
      - code
      - password
      - device
      properties:
        email:
          type: string
          format: email
          maxLength: 254
        code:
          type: string
          maxLength: 20
          description: The 6 digits from the email.
        password:
          type: string
          minLength: 10
          maxLength: 256
          writeOnly: true
          description: 'Must also be hard to guess: passwords built from common words, the person''s name or email, dates, repeats, or keyboard runs are refused (field "password").'
        device:
          $ref: '#/components/schemas/DeviceInfo'
    FieldError:
      type: object
      required:
      - field
      - detail
      properties:
        field:
          type: string
          description: JSON path of the invalid field, for example `ops[2].path`.
        detail:
          type: string
    MFAChallenge:
      type: object
      required:
      - mfa_token
      - expires_in
      - methods
      properties:
        mfa_token:
          type: string
        expires_in:
          type: integer
          description: Seconds left to send the second factor.
        methods:
          type: array
          items:
            type: string
            enum:
            - totp
            - recovery_code
    User:
      type: object
      required:
      - id
      - email
      - name
      - email_verified
      - two_factor_enabled
      - created_at
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        name:
          type: string
        email_verified:
          type: boolean
        two_factor_enabled:
          type: boolean
        created_at:
          type: string
          format: date-time
        avatar_url:
          type: string
          description: The profile photo's path on the API server (`/v1/avatars/{id}`); absent without one.
    SignupRequest:
      type: object
      required:
      - email
      - password
      properties:
        email:
          type: string
          format: email
          maxLength: 254
        password:
          type: string
          minLength: 10
          maxLength: 256
          writeOnly: true
          description: 'Must also be hard to guess: passwords built from common words, the person''s name or email, dates, repeats, or keyboard runs are refused (field "password").'
        name:
          type: string
          maxLength: 200
        workspace_name:
          type: string
          minLength: 1
          maxLength: 100
          description: 'Signs up for a business account: a new account also gets a workspace of this name,

            owned by it, on the 30-day Business trial. Ignored when the email already has an account.

            '
        confirm:
          $ref: '#/components/schemas/EmailConfirmMode'
    Problem:
      type: object
      description: RFC 9457 problem details.
      required:
      - type
      - title
      - status
      properties:
        type:
          type: string
          format: uri
          examples:
          - https://granite.md/problems/conflict
        title:
          type: string
        status:
          type: integer
          format: int32
        detail:
          type: string
        instance:
          type: string
        code:
          type: string
          description: Machine-readable refinement of `type`, for example `version_conflict`.
        request_id:
          type: string
        errors:
          type: array
          description: Per-field validation errors.
          items:
            $ref: '#/components/schemas/FieldError'
    Accepted:
      type: object
      required:
      - status
      properties:
        status:
          type: string
          enum:
          - accepted
    PasswordResetConfirmRequest:
      type: object
      required:
      - token
      - password
      properties:
        token:
          type: string
          maxLength: 200
        password:
          type: string
          minLength: 10
          maxLength: 256
          writeOnly: true
          description: 'Must also be hard to guess: passwords built from common words, the person''s name or email, dates, repeats, or keyboard runs are refused (field "password").'
    LoginRequest:
      type: object
      required:
      - email
      - password
      - device
      properties:
        email:
          type: string
          format: email
          maxLength: 254
        password:
          type: string
          maxLength: 256
          writeOnly: true
        device:
          $ref: '#/components/schemas/DeviceInfo'
    TokenResponse:
      type: object
      required:
      - access_token
      - token_type
      - expires_in
      - refresh_token
      - refresh_expires_in
      - device_id
      - user
      properties:
        access_token:
          type: string
        token_type:
          type: string
          enum:
          - Bearer
        expires_in:
          type: integer
          description: Seconds until the access token expires.
        refresh_token:
          type: string
          description: Single-use. Store it in the OS keychain.
        refresh_expires_in:
          type: integer
        device_id:
          type: string
          format: uuid
        user:
          $ref: '#/components/schemas/User'
    DeviceInfo:
      type: object
      required:
      - platform
      properties:
        id:
          type: string
          format: uuid
          description: Device ID from an earlier sign-in, to keep the same device record.
        name:
          type: string
          maxLength: 100
          examples:
          - Fuad's MacBook
        platform:
          type: string
          enum:
          - web
          - macos
          - windows
          - ios
          - android
          - cli
          - other
        app_version:
          type: string
          maxLength: 50
    SocialLoginRequest:
      type: object
      required:
      - id_token
      - nonce
      - device
      properties:
        id_token:
          type: string
          maxLength: 8000
        nonce:
          type: string
          minLength: 16
          maxLength: 200
        name:
          type: string
          maxLength: 200
          description: Apple gives the user's name to the app only on the first sign-in.
        authorization_code:
          type: string
          maxLength: 2000
          description: 'Apple only: the authorization code Apple returned with the ID token. The server

            exchanges it for a refresh token, kept encrypted, so that deleting the account can

            revoke Granite''s access to the person''s Apple ID (App Store Review Guideline 5.1.1(v)).

            A failed exchange does not stop the sign-in.

            '
        workspace_name:
          type: string
          minLength: 1
          maxLength: 100
          description: 'Signs up for a business account (with a company''s Google Workspace account, say): when

            this sign-in creates the account, it also gets a workspace of this name, owned by it,

            on the 30-day Business trial. Ignored for an existing account.

            '
        device:
          $ref: '#/components/schemas/DeviceInfo'
    EmailConfirmMode:
      type: string
      enum:
      - link
      - code
      default: link
      description: 'How the email proves the address: a link to open (`link`, the web) or a 6-digit code to

        type into the app (`code`, the mobile apps).

        '
    EmailCodeRequest:
      type: object
      required:
      - email
      - code
      properties:
        email:
          type: string
          format: email
          maxLength: 254
        code:
          type: string
          maxLength: 20
          description: The 6 digits from the email.
    StepUpRequest:
      type: object
      required:
      - password
      properties:
        password:
          type: string
          maxLength: 256
          writeOnly: true
        code:
          type: string
          maxLength: 10
          description: Authenticator code, required when two-factor authentication is on.
    EmailConfirmRequest:
      type: object
      required:
      - email
      properties:
        email:
          type: string
          format: email
          maxLength: 254
        confirm:
          $ref: '#/components/schemas/EmailConfirmMode'
    RefreshRequest:
      type: object
      required:
      - refresh_token
      properties:
        refresh_token:
          type: string
          maxLength: 200
    SignupConfirmRequest:
      type: object
      required:
      - email
      - code
      - device
      properties:
        email:
          type: string
          format: email
          maxLength: 254
        code:
          type: string
          maxLength: 20
          description: The 6 digits from the email.
        device:
          $ref: '#/components/schemas/DeviceInfo'
    SecondFactorRequest:
      type: object
      required:
      - mfa_token
      properties:
        mfa_token:
          type: string
          maxLength: 2000
        code:
          type: string
          maxLength: 10
        recovery_code:
          type: string
          maxLength: 40
  responses:
    Accepted:
      description: Request accepted. The same answer is given whether or not an account exists.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Accepted'
    TooManyRequests:
      description: Rate limit exceeded.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Error:
      description: Error response.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Session access token, personal API token, or OAuth access token.