openapi: 3.0.3
info:
  title: ZoraLinks API
  version: "1.0"
  description: |
    API REST/JSON para crear y gestionar enlaces, consultar saldo y leer
    analítica por enlace. La clave se envía como Bearer y nunca debe exponerse
    en frontend.
servers:
  - url: https://zoralinks.com/api/v1
security:
  - bearerAuth: []
paths:
  /me:
    get:
      summary: Usuario y datos de la clave
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Me"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /links:
    get:
      summary: Lista enlaces paginados
      parameters:
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
        - name: offset
          in: query
          schema: { type: integer, minimum: 0, default: 0 }
        - name: status
          in: query
          schema: { type: string, enum: [active, paused] }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Link" }
                  pagination: { $ref: "#/components/schemas/Pagination" }
        "401":
          $ref: "#/components/responses/Unauthorized"
    post:
      summary: Crea un enlace y lo registra en la red
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/LinkInput" }
      responses:
        "201":
          description: Creado
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Link" }
        "403":
          description: Clave test sin permiso de escritura
        "409":
          description: Alias en uso
        "422":
          description: Datos inválidos
        "429":
          description: Límite de 10 enlaces por hora
        "502":
          description: La red no registró el enlace
  /links/{idOrAlias}:
    parameters:
      - $ref: "#/components/parameters/IdOrAlias"
    get:
      summary: Detalle de un enlace
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Link" }
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      summary: Actualiza un enlace
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/LinkUpdate" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Link" }
        "422":
          description: Datos inválidos o alias inmutable
        "502":
          description: La red no confirmó el cambio
    put:
      summary: Igual que PATCH
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/LinkUpdate" }
      responses:
        "200":
          description: OK
    delete:
      summary: Borra un enlace y lo retira de la red
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  remote_deleted: { type: boolean }
                  warning: { type: string }
        "404":
          $ref: "#/components/responses/NotFound"
  /stats:
    get:
      summary: Totales y saldo local
      parameters:
        - $ref: "#/components/parameters/Period"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Stats" }
  /analytics:
    get:
      summary: Analítica por enlace
      parameters:
        - $ref: "#/components/parameters/Period"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Analytics" }
  /links/{idOrAlias}/analytics:
    parameters:
      - $ref: "#/components/parameters/IdOrAlias"
    get:
      summary: Analítica de un enlace
      parameters:
        - $ref: "#/components/parameters/Period"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Analytics" }
        "404":
          $ref: "#/components/responses/NotFound"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: zl_live_... o zl_test_...
  parameters:
    IdOrAlias:
      name: idOrAlias
      in: path
      required: true
      schema: { type: string }
    Period:
      name: period
      in: query
      schema:
        type: string
        enum: [7d, 14d, 30d, all]
        default: 30d
  responses:
    Unauthorized:
      description: Clave ausente, inválida o revocada
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: Enlace no encontrado en la cuenta
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
  schemas:
    Error:
      type: object
      properties:
        error: { type: string }
        message: { type: string }
    Me:
      type: object
      properties:
        data:
          type: object
          properties:
            id: { type: string }
            email: { type: string }
            name: { type: string }
            currency: { type: string, example: USD }
    LinkInput:
      type: object
      required: [alias, destination]
      properties:
        alias: { type: string, minLength: 3, maxLength: 48, pattern: "^[a-z0-9-]+$" }
        destination: { type: string, format: uri }
        title: { type: string, maxLength: 120 }
        steps: { type: integer, minimum: 2, maximum: 6, default: 3 }
    LinkUpdate:
      type: object
      properties:
        title: { type: string, maxLength: 120 }
        destination: { type: string, format: uri }
        steps: { type: integer, minimum: 2, maximum: 6 }
        status: { type: string, enum: [active, paused] }
    Link:
      type: object
      properties:
        id: { type: string }
        alias: { type: string }
        title: { type: string }
        destination: { type: string }
        short_url: { type: string, format: uri }
        steps: { type: integer }
        status: { type: string }
        source: { type: string }
        clicks: { type: integer }
        valid_clicks: { type: number }
        paid_clicks: { type: number, nullable: true }
        cpm_usd_cents: { type: integer, nullable: true }
        cpm_usd: { type: number, nullable: true }
        revenue_cents: { type: number }
        currency: { type: string, example: USD }
        ctr: { type: number, nullable: true }
        metrics_source: { type: string }
        created_date: { type: string, format: date-time }
        updated_date: { type: string, format: date-time, nullable: true }
    Pagination:
      type: object
      properties:
        limit: { type: integer }
        offset: { type: integer }
        total: { type: integer }
        has_more: { type: boolean }
    Stats:
      type: object
      properties:
        data:
          type: object
          additionalProperties: true
    Analytics:
      type: object
      properties:
        source: { type: string }
        period:
          type: object
          properties:
            key: { type: string }
            from: { type: string, format: date-time }
            to: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        totals:
          type: object
          properties:
            clicks: { type: integer }
            valid_clicks: { type: number }
            links: { type: integer }
            partner_revenue_cents: { type: number }
            currency: { type: string, example: USD }
        links:
          type: array
          items: { $ref: "#/components/schemas/Link" }
