{
  "openapi": "3.1.0",
  "info": {
    "title": "Flow2FA OTP API",
    "version": "1.0.0",
    "summary": "Generate and validate one-time passcodes (OTP) over email and SMS.",
    "description": "Public REST API for the Flow2FA OTP platform, served from https://api.flow2fa.com. The website (https://flow2fa.com) and the API use separate origins; production API access is provisioned during account setup. Two endpoints are exposed: POST /otp/generate creates an OTP and queues it for delivery, and POST /otp/validate verifies a user-entered OTP and consumes it. The OTP value is not returned by the API. Call both endpoints from your backend so the API key is never exposed to a browser or mobile client. This document describes only the publicly documented REST surface; SMPP v3.4 connections are provisioned separately during account setup.",
    "contact": {
      "name": "Flow2FA developer support",
      "email": "devs@flow2fa.com",
      "url": "https://flow2fa.com/documentation"
    },
    "license": {
      "name": "Flow2FA Terms of Service",
      "url": "https://flow2fa.com/terms"
    }
  },
  "servers": [
    {
      "url": "https://api.flow2fa.com",
      "description": "Production REST API origin. Access is provisioned during account setup."
    }
  ],
  "externalDocs": {
    "description": "Flow2FA API documentation",
    "url": "https://flow2fa.com/documentation"
  },
  "tags": [
    {
      "name": "OTP",
      "description": "One-time passcode generation and validation."
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/otp/generate": {
      "post": {
        "operationId": "generateOtp",
        "tags": ["OTP"],
        "summary": "Generate an OTP and queue it for delivery",
        "description": "Generates a one-time passcode and queues it for delivery via the selected channel. The OTP value is not returned by the API \u2014 only metadata and the server-generated otpId, which is used later to validate the code. The provider field must carry a value configured for your account and channel; request examples in this document are illustrative only.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerateOtpRequest"
              },
              "example": {
                "channel": "email",
                "provider": "YOUR_CONFIGURED_PROVIDER",
                "destination": "test@email.com",
                "type": "alphanumeric",
                "length": 8,
                "expiresInSeconds": 300,
                "templateId": "otp-default-a1562ab2"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OTP created and queued for delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerateOtpResponse"
                },
                "example": {
                  "otpId": "51a6fcc7-efc8-4e69-8bc1-ebb999befb2c",
                  "destination": "test@email.com",
                  "createdAt": "2025-09-10T14:05:41.5251815Z",
                  "expiresAt": "2025-09-10T14:10:41.5251815Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation error. Documented messages include: \"OTP channel is required and must be either 'Email' or 'Sms'.\", \"Destination (email or phone number) is required.\", \"OTP type is required.\", \"OTP length is required.\", \"ExpiresInSeconds is required.\", \"Invalid email format. It must be like name@example.com.\", \"Invalid phone number format. Must start with country code.\", \"Provider not found for this client and channel.\", \"OTP length must be between 4 and 12.\", \"ExpiresInSeconds must be greater than zero.\"",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "missingChannel": {
                    "summary": "Missing channel",
                    "value": {
                      "message": "OTP channel is required and must be either 'Email' or 'Sms'."
                    }
                  },
                  "invalidEmail": {
                    "summary": "Invalid email format",
                    "value": {
                      "message": "Invalid email format. It must be like name@example.com."
                    }
                  },
                  "invalidPhone": {
                    "summary": "Invalid phone format (SMS)",
                    "value": {
                      "message": "Invalid phone number format. Must start with country code."
                    }
                  },
                  "unknownProvider": {
                    "summary": "Unknown or unconfigured provider",
                    "value": {
                      "message": "Provider not found for this client and channel."
                    }
                  },
                  "lengthOutOfRange": {
                    "summary": "OTP length out of range",
                    "value": {
                      "message": "OTP length must be between 4 and 12."
                    }
                  },
                  "nonPositiveExpiry": {
                    "summary": "Non-positive expiration",
                    "value": {
                      "message": "ExpiresInSeconds must be greater than zero."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/otp/validate": {
      "post": {
        "operationId": "validateOtp",
        "tags": ["OTP"],
        "summary": "Validate a user-entered OTP",
        "description": "Validates a previously generated OTP using the otpId returned by /otp/generate and the code entered by the user. On success the OTP is consumed (single use) and cannot be reused. The destination is not sent when validating \u2014 the otpId carries the context. If the allowed attempt count configured for the provider is exceeded, further validations return \"Too many failed attempts. Try again later.\" \u2014 even when the OTP is correct \u2014 until the lock duration elapses.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ValidateOtpRequest"
              },
              "example": {
                "otpId": "c4f8814c-7a9d-4e13-b7d8-d4e66d272611",
                "otp": "z4dGyBPQ"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OTP is valid and has been consumed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidateOtpResponse"
                },
                "example": {
                  "success": true,
                  "message": "OTP validated successfully."
                }
              }
            }
          },
          "400": {
            "description": "Validation error. Documented messages include: \"OtpId is required.\", \"Otp is required.\", \"Invalid OTP or expired.\", \"Too many failed attempts. Try again later.\"",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "missingOtpId": {
                    "summary": "Missing otpId",
                    "value": {
                      "message": "OtpId is required."
                    }
                  },
                  "missingOtp": {
                    "summary": "Missing otp",
                    "value": {
                      "message": "Otp is required."
                    }
                  },
                  "invalidOrExpired": {
                    "summary": "Incorrect or expired OTP",
                    "value": {
                      "message": "Invalid OTP or expired."
                    }
                  },
                  "tooManyAttempts": {
                    "summary": "Too many failed attempts (temporary lock)",
                    "value": {
                      "message": "Too many failed attempts. Try again later."
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Your Flow2FA API key, issued during account setup. Keep it server-side \u2014 never ship it to a browser or mobile client."
      }
    },
    "schemas": {
      "GenerateOtpRequest": {
        "type": "object",
        "required": ["channel", "provider", "destination", "type", "length", "expiresInSeconds"],
        "additionalProperties": false,
        "properties": {
          "channel": {
            "type": "string",
            "enum": ["email", "sms"],
            "description": "Delivery channel."
          },
          "provider": {
            "type": "string",
            "description": "Required. The account-configured provider or delivery-profile value issued during onboarding for this channel. Credentials are managed server-side and are never sent in the request. Whether a permitted value maps to one underlying vendor or to an account-specific managed routing arrangement depends on the configuration provisioned for the account and is confirmed during setup. Flow2FA does not document automatic provider switching, overriding of the submitted value, or a universal fallback. Values listed under examples are illustrative only \u2014 use the values issued for your account. A value that is not configured for the account and channel returns 400.",
            "examples": ["YOUR_CONFIGURED_PROVIDER"]
          },
          "destination": {
            "type": "string",
            "description": "Recipient: an email address (for email) or an E.164 phone number (for sms, e.g. +573001112233).",
            "examples": ["test@email.com", "+573001112233"]
          },
          "type": {
            "type": "string",
            "enum": ["numeric", "alphanumeric"],
            "description": "OTP type."
          },
          "length": {
            "type": "integer",
            "minimum": 4,
            "maximum": 12,
            "description": "OTP length between 4 and 12.",
            "examples": [6, 8]
          },
          "expiresInSeconds": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "description": "OTP time-to-live in seconds.",
            "examples": [300]
          },
          "templateId": {
            "type": "string",
            "description": "Optional message template to render.",
            "examples": ["otp-default-a1562ab2"]
          }
        }
      },
      "GenerateOtpResponse": {
        "type": "object",
        "required": ["otpId", "destination", "createdAt", "expiresAt"],
        "properties": {
          "otpId": {
            "type": "string",
            "description": "Server-generated unique identifier for the OTP. Use it later to validate the code."
          },
          "destination": {
            "type": "string",
            "description": "Target email address or phone number."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "UTC timestamp when the OTP was created."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "UTC timestamp when the OTP expires."
          }
        }
      },
      "ValidateOtpRequest": {
        "type": "object",
        "required": ["otpId", "otp"],
        "additionalProperties": false,
        "properties": {
          "otpId": {
            "type": "string",
            "description": "The identifier returned by /otp/generate."
          },
          "otp": {
            "type": "string",
            "description": "The OTP code entered by the user."
          }
        }
      },
      "ValidateOtpResponse": {
        "type": "object",
        "required": ["success", "message"],
        "properties": {
          "success": {
            "type": "boolean",
            "description": "true when the OTP is valid and accepted."
          },
          "message": {
            "type": "string",
            "description": "Human-readable confirmation message."
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "description": "Human-readable error message describing why the request was rejected."
          }
        }
      }
    }
  }
}
