openapi: 3.1.0
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
info:
  title: Flowstates Public API
  version: 2026-04-16
  summary: Public API for Flowstates URL short links and OTP generation/validation.
  description: |
    This OpenAPI document is a conservative transcription of Flowstates' published
    API documentation, reviewed on 2026-04-16.

    It intentionally avoids inventing undocumented behavior. Where the source
    material is ambiguous, that ambiguity is called out in descriptions or
    specification extensions.
  contact:
    name: Flowstates
    url: https://flowstates.net/
servers:
  - url: https://cpt.cx
    description: Production API server
security:
  - ApiKeyAuth: []
tags:
  - name: URL Shortener
    description: Create public short links individually or in batches.
  - name: OTP
    description: Generate one-time passwords and validate them later.
externalDocs:
  description: Flowstates website
  url: https://flowstates.net/
x-source-material:
  reviewedOn: 2026-04-16
  sources:
    - name: URL Shortener Postman docs
      url: https://documenter.getpostman.com/view/36638207/2sB34bMjCq
    - name: OTP API Postman docs
      url: https://documenter.getpostman.com/view/36638207/2sB3HnMLco
x-source-gaps:
  - The sample request for POST /otp/generate includes templateId, but the request field table does not document it.
  - The batch short-link docs do not show the exact response body shape for per-item failures.
  - The published docs do not document auth failures, rate limits, generic 5xx responses, or max batch size.
  - The short-link docs say the default expiry is 30 days, but do not say whether expiry is configurable through the public API.
  - The OTP validate docs mention a temporary lock after too many failed attempts, but do not document the lock duration.
paths:
  /shortlink/public-generate:
    post:
      tags:
        - URL Shortener
      operationId: createPublicShortLink
      summary: Create one public short link
      description: |
        Creates a public short link by accepting a destination URL, a display
        name, and a domain registered to the authenticated client.

        The published docs state that only http and https URLs are accepted and
        that the default expiry is 30 days after creation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ShortLinkCreateRequest'
            examples:
              basic:
                summary: Create one short link
                value:
                  name: BBC News
                  url: https://www.bbc.com/news
                  domain: cpt.cx
                  tags:
                    - News
                    - BBC
      responses:
        '200':
          description: Short link created successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShortLinkCreateResponse'
              examples:
                success:
                  value:
                    name: BBC News
                    shortUrl: https://cpt.cx/RRRRRF
                    createdDate: '2025-07-02T15:38:02.9554085Z'
                    expiryDate: '2025-08-01T15:38:02.9554203Z'
        '400':
          $ref: '#/components/responses/ShortLinkValidationError'
      externalDocs:
        description: Source Postman docs
        url: https://documenter.getpostman.com/view/36638207/2sB34bMjCq
  /shortlink/public-generate-batch:
    post:
      tags:
        - URL Shortener
      operationId: createPublicShortLinksBatch
      summary: Create multiple public short links
      description: |
        Creates multiple public short links in a single request. Each input item
        uses the same field rules as the single short-link creation endpoint.

        The published docs show a summary with success and failed counts plus a
        results array of successful items, but they do not show the exact
        response shape for individual failed items.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/ShortLinkCreateRequest'
            examples:
              batch:
                summary: Create two short links
                value:
                  - name: BBC News
                    url: https://www.bbc.com/news
                    domain: cpt.cx
                  - name: Facebook
                    url: https://www.facebook.com
                    domain: cpt.cx
                    tags:
                      - News
                      - World
      responses:
        '200':
          description: Batch processed successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShortLinkBatchResponse'
              examples:
                success:
                  value:
                    summary:
                      successCount: 2
                      failedCount: 0
                    results:
                      - name: BBC News
                        shortUrl: https://cpt.cx/RRRRRT
                        createdDate: '2025-07-02T15:42:05.2432041Z'
                        expiryDate: '2025-08-01T15:42:05.2432043Z'
                      - name: Facebook
                        shortUrl: https://cpt.cx/RRRRRa
                        createdDate: '2025-07-02T15:42:05.4787264Z'
                        expiryDate: '2025-08-01T15:42:05.4787265Z'
        '400':
          $ref: '#/components/responses/ShortLinkValidationError'
      externalDocs:
        description: Source Postman docs
        url: https://documenter.getpostman.com/view/36638207/2sB34bMjCq
  /otp/generate:
    post:
      tags:
        - OTP
      operationId: generateOtp
      summary: Generate an OTP and queue it for delivery
      description: |
        Generates a one-time password and queues it for delivery through the
        selected channel.

        The published docs state that the OTP value is never returned and is not
        stored in plaintext.

        Note: a sample request in the source material includes templateId, but
        that field is not documented in the request-body table and is
        intentionally omitted from this schema pending confirmation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OtpGenerateRequest'
            examples:
              email:
                summary: Generate an email OTP
                value:
                  channel: email
                  provider: generic
                  destination: test@ejemplo.com
                  type: alphanumeric
                  length: 8
                  expiresInSeconds: 300
      responses:
        '200':
          description: OTP accepted for generation and delivery.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OtpGenerateResponse'
              examples:
                success:
                  value:
                    otpId: 51a6fcc7-efc8-4e69-8bc1-ebb999befb2c
                    destination: test@ejemplo.com
                    createdAt: '2025-09-10T14:05:41.5251815Z'
                    expiresAt: '2025-09-10T14:10:41.5251815Z'
        '4XX':
          $ref: '#/components/responses/OtpGenerateClientError'
      externalDocs:
        description: Source Postman docs
        url: https://documenter.getpostman.com/view/36638207/2sB3HnMLco
  /otp/validate:
    post:
      tags:
        - OTP
      operationId: validateOtp
      summary: Validate a previously generated OTP
      description: |
        Validates a previously generated OTP using the otpId returned by
        POST /otp/generate and the user-entered OTP value.

        The published docs say that a successful validation immediately consumes
        the OTP and that the OTP cannot be reused.

        The Postman request definition only explicitly listed x-api-key in the
        headers, but the documentation text also mentions Content-Type:
        application/json. This spec assumes JSON request bodies and includes
        Content-Type accordingly.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OtpValidateRequest'
            examples:
              validate:
                summary: Validate a generated OTP
                value:
                  otpId: c4f8814c-7a9d-4e13-b7d8-d4e66d272611
                  otp: z4dGyBPQ
      responses:
        '200':
          description: OTP validated successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OtpValidateResponse'
              examples:
                success:
                  value:
                    success: true
                    message: OTP validated successfully.
        '4XX':
          $ref: '#/components/responses/OtpValidateClientError'
      externalDocs:
        description: Source Postman docs
        url: https://documenter.getpostman.com/view/36638207/2sB3HnMLco
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: API key required for all documented public endpoints.
  schemas:
    ShortLinkCreateRequest:
      type: object
      required:
        - name
        - url
        - domain
      properties:
        name:
          type: string
          description: Descriptive name for the short link.
          examples:
            - BBC News
        url:
          type: string
          format: uri
          description: Destination URL. The published docs restrict this to http and https URLs.
          examples:
            - https://www.bbc.com/news
        domain:
          type: string
          description: Domain registered to the authenticated client.
          examples:
            - cpt.cx
        tags:
          type: array
          description: Optional tags associated with the short link.
          items:
            type: string
          examples:
            - - News
              - BBC
        username:
          type: string
          description: Optional username for basic-auth protection. Must be used together with password.
        password:
          type: string
          description: Optional password for basic-auth protection. Must be used together with username.
      dependentRequired:
        username:
          - password
        password:
          - username
    ShortLinkCreateResponse:
      type: object
      required:
        - name
        - shortUrl
        - createdDate
        - expiryDate
      properties:
        name:
          type: string
          description: The name sent in the request.
        shortUrl:
          type: string
          format: uri
          description: The generated short-link URL.
        createdDate:
          type: string
          format: date-time
          description: Timestamp when the short link was created.
        expiryDate:
          type: string
          format: date-time
          description: Timestamp when the short link expires.
    ShortLinkBatchResponse:
      type: object
      required:
        - summary
        - results
      properties:
        summary:
          type: object
          required:
            - successCount
            - failedCount
          properties:
            successCount:
              type: integer
              minimum: 0
              description: Number of successfully created short links.
            failedCount:
              type: integer
              minimum: 0
              description: Number of short links that failed validation.
        results:
          type: array
          description: Successfully created short links.
          items:
            $ref: '#/components/schemas/ShortLinkCreateResponse'
    OtpGenerateRequest:
      type: object
      required:
        - channel
        - provider
        - destination
        - type
        - length
        - expiresInSeconds
      properties:
        channel:
          type: string
          enum:
            - email
            - sms
          description: Delivery channel.
        provider:
          type: string
          description: |
            Provider name configured in Flowstates for the authenticated client and
            the selected channel. The published docs include examples like
            generic, twilio, and sendgrid, but do not define a universal enum.
        destination:
          type: string
          description: |
            Recipient email address for email OTPs, or an E.164 phone number for
            SMS OTPs.
        type:
          type: string
          enum:
            - numeric
            - alphanumeric
          description: OTP type.
        length:
          type: integer
          minimum: 4
          maximum: 12
          description: OTP length. Published docs state the valid range is 4 to 12.
        expiresInSeconds:
          type: integer
          minimum: 1
          description: OTP time-to-live in seconds.
    OtpGenerateResponse:
      type: object
      required:
        - otpId
        - destination
        - createdAt
        - expiresAt
      properties:
        otpId:
          type: string
          format: uuid
          description: Unique identifier used later to validate the OTP.
        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.
    OtpValidateRequest:
      type: object
      required:
        - otpId
        - otp
      properties:
        otpId:
          type: string
          format: uuid
          description: Identifier returned by POST /otp/generate.
        otp:
          type: string
          description: OTP code entered by the end user.
    OtpValidateResponse:
      type: object
      required:
        - success
        - message
      properties:
        success:
          type: boolean
          const: true
          description: true when the OTP is valid and accepted.
        message:
          type: string
          description: Human-readable confirmation message.
    ErrorMessage:
      type: string
      description: |
        The published docs show error messages as bare strings. They do not
        document a wrapper object shape or a definitive error content type.
  responses:
    ShortLinkValidationError:
      description: Validation errors documented for the URL Shortener API.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorMessage'
          examples:
            missingRequiredFields:
              value: 'Missing required fields: name, url, domain'
            invalidUrlFormat:
              value: 'Invalid URL format.'
            urlNotAccessible:
              value: 'URL is not accessible.'
            unregisteredDomain:
              value: 'Domain is not registered for this client.'
            usernameMissingPassword:
              value: 'If a username is provided, a password must also be specified.'
            passwordMissingUsername:
              value: 'If a password is provided, a username must also be specified.'
        text/plain:
          schema:
            $ref: '#/components/schemas/ErrorMessage'
          examples:
            missingRequiredFields:
              value: 'Missing required fields: name, url, domain'
    OtpGenerateClientError:
      description: |
        Client error responses documented for OTP generation. The published docs
        list validation failures but do not specify the exact status code for
        every case, so this spec uses 4XX.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorMessage'
          examples:
            missingChannel:
              value: "OTP channel is required and must be either 'Email' or 'Sms'."
            missingDestination:
              value: 'Destination (email or phone number) is required.'
            missingType:
              value: 'OTP type is required.'
            missingLength:
              value: 'OTP length is required.'
            missingExpiry:
              value: 'ExpiresInSeconds is required.'
            invalidEmail:
              value: 'Invalid email format. It must be like name@example.com.'
            invalidPhone:
              value: 'Invalid phone number format. Must start with country code'
            providerNotFound:
              value: 'Provider not found for this client and channel.'
            invalidLength:
              value: 'OTP length must be between 4 and 12.'
            invalidExpiry:
              value: 'ExpiresInSeconds must be greater than zero.'
        text/plain:
          schema:
            $ref: '#/components/schemas/ErrorMessage'
          examples:
            providerNotFound:
              value: 'Provider not found for this client and channel.'
    OtpValidateClientError:
      description: |
        Client error responses documented for OTP validation. The published docs
        list the message text but do not specify the exact status code for every
        case, so this spec uses 4XX.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorMessage'
          examples:
            missingOtpId:
              value: 'OtpId is required.'
            missingOtp:
              value: 'Otp is required.'
            invalidOrExpired:
              value: 'Invalid OTP or expired.'
            tooManyAttempts:
              value: 'Too many failed attempts. Try again later.'
        text/plain:
          schema:
            $ref: '#/components/schemas/ErrorMessage'
          examples:
            invalidOrExpired:
              value: 'Invalid OTP or expired.'
