openapi: 3.0.3
info:
  title: QRCode Pangeia API
  description: |
    API pública e gratuita para geração de QR Codes em múltiplos formatos padronizados
    (texto, URL, e-mail, telefone, SMS, WhatsApp, Wi-Fi, vCard, geolocalização, evento/calendário e Pix),
    com suporte a estilos visuais, gradientes e logotipo embutido.

    * **Sem autenticação**: uso liberado com limite de requisições por IP.
    * **Com API Key** (`X-API-Key`): limites maiores e estatísticas de uso — gere a sua gratuitamente em
      [/dashboard/apikeys](/dashboard/apikeys) após entrar com Pangeia ID.
    * **Plano PRO**: limites ainda maiores, via assinatura mensal (Pangeia Pay).

    Login opcional via **Pangeia ID** permite salvar, editar e consultar o histórico completo de QR Codes gerados.
  version: "1.0.0"
  contact:
    name: Pangeia Labs
    url: https://qrcode.pangeialabs.com
servers:
  - url: https://qrcode.pangeialabs.com
    description: Produção
  - url: http://localhost:5000
    description: Local

tags:
  - name: QR Codes
    description: Geração, consulta, edição e remoção de QR Codes
  - name: Tipos & Estilos
    description: Metadados sobre tipos suportados, estilos e gradientes
  - name: Conta
    description: Histórico e uso (requer login ou API Key)
  - name: API Keys
    description: Gestão de chaves de API (requer login)

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Opcional. Aumenta os limites de requisição e habilita estatísticas de uso.

  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string, example: VALIDATION_ERROR }
            message: { type: string, example: "url é obrigatório" }
            field: { type: string, example: url }

    QRTypeField:
      type: object
      properties:
        name: { type: string }
        label: { type: string }
        type: { type: string, enum: [text, textarea, url, email, tel, password, number, datetime-local, select, checkbox] }
        required: { type: boolean }
        placeholder: { type: string }
        options:
          type: array
          items:
            type: object
            properties:
              value: { type: string }
              label: { type: string }

    QRType:
      type: object
      properties:
        label: { type: string }
        description: { type: string }
        icon: { type: string }
        fields:
          type: array
          items: { $ref: "#/components/schemas/QRTypeField" }

    StyleChoice:
      type: object
      properties:
        value: { type: string, example: rounded }
        label: { type: string, example: "Arredondado" }

    TypesResponse:
      type: object
      properties:
        types:
          type: object
          additionalProperties: { $ref: "#/components/schemas/QRType" }
        styles:
          type: array
          items: { $ref: "#/components/schemas/StyleChoice" }
        gradients:
          type: array
          items: { $ref: "#/components/schemas/StyleChoice" }

    GenerateRequest:
      type: object
      required: [type, data]
      properties:
        type:
          type: string
          description: Um dos tipos retornados por `GET /api/v1/types`
          example: url
        data:
          type: object
          description: Campos específicos do tipo escolhido (ver `GET /api/v1/types`)
          example: { url: "https://pangeialabs.com" }
        style:
          type: string
          enum: [square, rounded, dots, gapped, vertical_bars, horizontal_bars]
          default: square
        fg_color: { type: string, example: "#111827" }
        bg_color: { type: string, example: "#ffffff" }
        gradient_color:
          type: string
          description: Se definido, ativa gradiente entre fg_color e gradient_color
          example: "#22d3ee"
        gradient_type:
          type: string
          enum: [radial, square, horizontal, vertical]
          default: radial
        error_correction:
          type: string
          enum: [L, M, Q, H]
          default: M
          description: Elevado automaticamente para H quando um logotipo é enviado
        logo_base64:
          type: string
          description: Logotipo em base64 (data URI). Alternativa ao upload multipart do campo `logo`.
        format:
          type: string
          enum: [json, png, svg]
          default: json
          description: Formato da resposta. `json` retorna base64; `png`/`svg` retornam o binário direto.
        save:
          type: boolean
          default: false
          description: Salva o QR Code no acervo do usuário autenticado (requer login).
        title:
          type: string
          description: Título opcional para exibição no acervo (usado apenas com save=true)

    GenerateResponse:
      type: object
      properties:
        type: { type: string }
        payload: { type: string, description: "Conteúdo textual codificado no QR Code" }
        image_base64: { type: string, description: "Data URI PNG (data:image/png;base64,...)" }
        saved:
          $ref: "#/components/schemas/QRCodeRecord"

    QRCodeRecord:
      type: object
      properties:
        id: { type: string, example: "qr_AbC123..." }
        type: { type: string }
        title: { type: string }
        payload: { type: string }
        style: { type: string }
        fg_color: { type: string }
        bg_color: { type: string }
        gradient_color: { type: string, nullable: true }
        has_logo: { type: boolean }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        scan_count: { type: integer }
        image_url: { type: string }

    HistoryResponse:
      type: object
      properties:
        total: { type: integer }
        page: { type: integer }
        per_page: { type: integer }
        items:
          type: array
          items: { $ref: "#/components/schemas/QRCodeRecord" }

    MeResponse:
      type: object
      properties:
        user:
          type: object
          nullable: true
        api_key_label: { type: string }
        tier: { type: string, enum: [free, pro] }
        rate_limit: { type: string, example: "300/hour" }
        request_count: { type: integer }
        last_used_at: { type: string, format: date-time, nullable: true }
        created_at: { type: string, format: date-time }

    ApiKey:
      type: object
      properties:
        id: { type: integer }
        key: { type: string }
        label: { type: string }
        created_at: { type: string, format: date-time }
        request_count: { type: integer }
        last_used_at: { type: string, format: date-time, nullable: true }

  responses:
    NotFound:
      description: Recurso não encontrado
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    Unauthorized:
      description: Autenticação necessária
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    ValidationError:
      description: Erro de validação nos dados enviados
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }

security:
  - {}
  - ApiKeyAuth: []

paths:
  /api/v1/types:
    get:
      tags: [Tipos & Estilos]
      summary: Lista todos os tipos de QR Code, estilos e gradientes suportados
      security: []
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TypesResponse" }

  /api/v1/qrcode:
    post:
      tags: [QR Codes]
      summary: Gera um QR Code
      description: |
        Aceita `application/json` (sem logotipo) ou `multipart/form-data` (para envio de logotipo via campo `logo`).
        No modo multipart, o campo `data` deve ser enviado como uma string JSON.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/GenerateRequest" }
          multipart/form-data:
            schema:
              type: object
              properties:
                type: { type: string }
                data: { type: string, description: "JSON stringificado com os campos do tipo" }
                style: { type: string }
                fg_color: { type: string }
                bg_color: { type: string }
                gradient_color: { type: string }
                gradient_type: { type: string }
                error_correction: { type: string }
                format: { type: string }
                save: { type: boolean }
                title: { type: string }
                logo: { type: string, format: binary }
      responses:
        "200":
          description: QR Code gerado com sucesso
          content:
            application/json:
              schema: { $ref: "#/components/schemas/GenerateResponse" }
            image/png: { schema: { type: string, format: binary } }
            image/svg+xml: { schema: { type: string } }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429":
          description: Limite de requisições excedido

  /api/v1/qrcode/{public_id}:
    parameters:
      - name: public_id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [QR Codes]
      summary: Consulta metadados de um QR Code salvo
      security: []
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/QRCodeRecord" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [QR Codes]
      summary: Atualiza um QR Code salvo (requer ser o dono)
      requestBody:
        content:
          application/json:
            schema: { $ref: "#/components/schemas/GenerateRequest" }
      responses:
        "200":
          description: Atualizado
          content:
            application/json:
              schema: { $ref: "#/components/schemas/QRCodeRecord" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [QR Codes]
      summary: Remove um QR Code salvo (requer ser o dono)
      responses:
        "200": { description: Removido }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /api/v1/qrcode/{public_id}/image:
    get:
      tags: [QR Codes]
      summary: Retorna a imagem (PNG ou SVG) de um QR Code salvo
      security: []
      parameters:
        - name: public_id
          in: path
          required: true
          schema: { type: string }
        - name: format
          in: query
          schema: { type: string, enum: [png, svg], default: png }
      responses:
        "200":
          description: Imagem do QR Code
          content:
            image/png: { schema: { type: string, format: binary } }
            image/svg+xml: { schema: { type: string } }
        "404": { $ref: "#/components/responses/NotFound" }

  /api/v1/history:
    get:
      tags: [Conta]
      summary: Lista o histórico de QR Codes do usuário autenticado
      parameters:
        - name: page
          in: query
          schema: { type: integer, default: 1 }
        - name: per_page
          in: query
          schema: { type: integer, default: 20, maximum: 100 }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/HistoryResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/v1/me:
    get:
      tags: [Conta]
      summary: Retorna informações da API Key autenticada e estatísticas de uso
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/MeResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/v1/apikeys:
    get:
      tags: [API Keys]
      summary: Lista as API Keys do usuário autenticado
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/ApiKey" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [API Keys]
      summary: Cria uma nova API Key (máximo 5 por conta)
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                label: { type: string, example: "Minha aplicação" }
      responses:
        "201":
          description: Criada
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ApiKey" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/v1/apikeys/{key_id}:
    parameters:
      - name: key_id
        in: path
        required: true
        schema: { type: integer }
    delete:
      tags: [API Keys]
      summary: Revoga uma API Key
      responses:
        "200": { description: Revogada }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
