openapi: 3.1.0
info:
  title: Noctra Public API
  version: "1.0.0"
  description: |
    Noctra Public API for projects, traffic and conversion lifecycle data.
x-i18n:
  locales:
    - en
    - pt-BR
    - es
  info:
    title:
      en: Noctra Public API
      pt-BR: API Pública Noctra
      es: API Pública de Noctra
    description:
      en: API for projects, traffic and conversion lifecycle data, including blog article submission.
      pt-BR: API para dados de projects, tráfego e ciclo de conversões, incluindo envio de artigos para o blog.
      es: API para datos de projects, tráfico y ciclo de conversiones, incluyendo envío de artículos para el blog.
  resources:
    projects:
      en: Projects
      pt-BR: Projetos
      es: Proyectos
    visits:
      en: Visits
      pt-BR: Visitas
      es: Visitas
    clicks:
      en: Clicks
      pt-BR: Cliques
      es: Clics
    checkouts:
      en: Checkouts
      pt-BR: Checkouts
      es: Checkouts
    sales:
      en: Sales
      pt-BR: Vendas
      es: Ventas
    refunds:
      en: Refunds
      pt-BR: Reembolsos
      es: Reembolsos
    upsells:
      en: Upsells
      pt-BR: Upsells
      es: Upsells
  errors:
    unauthorized:
      en: Unauthorized
      pt-BR: Não autorizado
      es: No autorizado
    forbidden:
      en: Forbidden
      pt-BR: Proibido
      es: Prohibido
    not_found:
      en: Resource not found
      pt-BR: Recurso não encontrado
      es: Recurso no encontrado
    validation_error:
      en: Validation error
      pt-BR: Erro de validação
      es: Error de validación
    rate_limit_exceeded:
      en: Rate limit exceeded
      pt-BR: Limite de requisições excedido
      es: Límite de solicitudes excedido
    internal_error:
      en: Internal server error
      pt-BR: Erro interno do servidor
      es: Error interno del servidor
servers:
  - url: https://api.noctra.org/v1
security:
  - bearerAuth: []
tags:
  - name: Projects
  - name: Visits
  - name: Clicks
  - name: Checkouts
  - name: Sales
  - name: Refunds
  - name: Upsells
  - name: Blog
paths:
  /projects:
    get:
      tags: [Projects]
      summary: List projects
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: Projects list
          headers:
            X-RateLimit-Limit: { $ref: "#/components/headers/XRateLimitLimit" }
            X-RateLimit-Remaining: { $ref: "#/components/headers/XRateLimitRemaining" }
            X-RateLimit-Reset: { $ref: "#/components/headers/XRateLimitReset" }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProjectListResponse"
  /projects/{project_id}:
    get:
      tags: [Projects]
      summary: Get project
      parameters:
        - name: project_id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Project
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProjectDetailResponse"
  /visits:
    get:
      tags: [Visits]
      summary: List visits
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/ProjectIdQuery"
        - $ref: "#/components/parameters/From"
        - $ref: "#/components/parameters/To"
        - $ref: "#/components/parameters/Campaign"
        - $ref: "#/components/parameters/AdGroup"
        - $ref: "#/components/parameters/Keyword"
        - $ref: "#/components/parameters/Device"
        - $ref: "#/components/parameters/Country"
      responses:
        "200":
          description: Visits list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VisitListResponse"
  /visits/{visit_id}:
    get:
      tags: [Visits]
      summary: Get visit
      parameters:
        - name: visit_id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Visit
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VisitDetailResponse"
  /clicks:
    get:
      tags: [Clicks]
      summary: List clicks
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/ProjectIdQuery"
        - $ref: "#/components/parameters/From"
        - $ref: "#/components/parameters/To"
        - $ref: "#/components/parameters/VisitId"
      responses:
        "200":
          description: Clicks list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClickListResponse"
  /clicks/{click_id}:
    get:
      tags: [Clicks]
      summary: Get click
      parameters:
        - name: click_id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Click
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClickDetailResponse"
  /checkouts:
    get:
      tags: [Checkouts]
      summary: List checkouts
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/ProjectIdQuery"
        - $ref: "#/components/parameters/From"
        - $ref: "#/components/parameters/To"
        - $ref: "#/components/parameters/VisitId"
        - $ref: "#/components/parameters/ClickId"
        - $ref: "#/components/parameters/Status"
        - $ref: "#/components/parameters/ProductId"
        - $ref: "#/components/parameters/OfferId"
      responses:
        "200":
          description: Checkouts list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CheckoutListResponse"
  /checkouts/{checkout_id}:
    get:
      tags: [Checkouts]
      summary: Get checkout
      parameters:
        - name: checkout_id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Checkout
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CheckoutDetailResponse"
  /sales:
    get:
      tags: [Sales]
      summary: List sales
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/ProjectIdQuery"
        - $ref: "#/components/parameters/From"
        - $ref: "#/components/parameters/To"
        - $ref: "#/components/parameters/VisitId"
        - $ref: "#/components/parameters/ClickId"
        - $ref: "#/components/parameters/CheckoutId"
        - $ref: "#/components/parameters/Status"
        - $ref: "#/components/parameters/ProductId"
        - $ref: "#/components/parameters/OfferId"
      responses:
        "200":
          description: Sales list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SaleListResponse"
  /sales/{sale_id}:
    get:
      tags: [Sales]
      summary: Get sale
      parameters:
        - name: sale_id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Sale
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SaleDetailResponse"
  /refunds:
    get:
      tags: [Refunds]
      summary: List refunds
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/ProjectIdQuery"
        - $ref: "#/components/parameters/From"
        - $ref: "#/components/parameters/To"
        - $ref: "#/components/parameters/SaleId"
        - $ref: "#/components/parameters/Status"
      responses:
        "200":
          description: Refunds list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RefundListResponse"
  /refunds/{refund_id}:
    get:
      tags: [Refunds]
      summary: Get refund
      parameters:
        - name: refund_id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Refund
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RefundDetailResponse"
  /upsells:
    get:
      tags: [Upsells]
      summary: List upsells
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/ProjectIdQuery"
        - $ref: "#/components/parameters/From"
        - $ref: "#/components/parameters/To"
        - $ref: "#/components/parameters/SaleId"
        - $ref: "#/components/parameters/Status"
      responses:
        "200":
          description: Upsells list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UpsellListResponse"
  /upsells/{upsell_id}:
    get:
      tags: [Upsells]
      summary: Get upsell
      parameters:
        - name: upsell_id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Upsell
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UpsellDetailResponse"
  /blog/articles:
    post:
      tags: [Blog]
      summary: Create blog article draft (requires support approval)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BlogArticleCreateRequest"
      responses:
        "201":
          description: Blog article created as draft
          headers:
            X-Blog-Write-Limit:
              schema: { type: string }
            X-Blog-Write-Remaining:
              schema: { type: string }
            X-Blog-Write-Reset:
              schema: { type: string }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlogArticleCreateResponse"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Missing support approval for blog write scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          description: Write rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
  parameters:
    Page:
      name: page
      in: query
      schema: { type: integer, minimum: 1, default: 1 }
    PerPage:
      name: per_page
      in: query
      schema: { type: integer, minimum: 1, maximum: 100, default: 25 }
    ProjectIdQuery:
      name: project_id
      in: query
      schema: { type: string, format: uuid }
    From:
      name: from
      in: query
      schema: { type: string, format: date-time }
    To:
      name: to
      in: query
      schema: { type: string, format: date-time }
    VisitId:
      name: visit_id
      in: query
      schema: { type: string }
    ClickId:
      name: click_id
      in: query
      schema: { type: string }
    CheckoutId:
      name: checkout_id
      in: query
      schema: { type: string }
    SaleId:
      name: sale_id
      in: query
      schema: { type: string }
    Status:
      name: status
      in: query
      schema: { type: string }
    ProductId:
      name: product_id
      in: query
      schema: { type: string }
    OfferId:
      name: offer_id
      in: query
      schema: { type: string }
    Campaign:
      name: campaign
      in: query
      schema: { type: string }
    AdGroup:
      name: ad_group
      in: query
      schema: { type: string }
    Keyword:
      name: keyword
      in: query
      schema: { type: string }
    Device:
      name: device
      in: query
      schema: { type: string }
    Country:
      name: country
      in: query
      schema: { type: string }
  headers:
    XRateLimitLimit:
      description: Requests allowed per minute.
      schema: { type: string }
    XRateLimitRemaining:
      description: Requests remaining in the current window.
      schema: { type: string }
    XRateLimitReset:
      description: Unix epoch timestamp for the next reset.
      schema: { type: string }
  schemas:
    Pagination:
      type: object
      properties:
        page: { type: integer }
        per_page: { type: integer }
        total: { type: integer }
        total_pages: { type: integer }
        has_next: { type: boolean }
        has_previous: { type: boolean }
        next_cursor: { type: string, nullable: true }
        prev_cursor: { type: string, nullable: true }
    Project:
      type: object
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        domain: { type: string, nullable: true }
        status: { type: string }
        created_at: { type: string, format: date-time }
    Visit:
      type: object
      properties:
        id: { type: string, format: uuid }
        project_id: { type: string }
        visitor_id: { type: string, nullable: true }
        utm_source: { type: string, nullable: true }
        utm_medium: { type: string, nullable: true }
        utm_campaign: { type: string, nullable: true }
        utm_content: { type: string, nullable: true }
        utm_term: { type: string, nullable: true }
        campaign: { type: string, nullable: true }
        ad_group: { type: string, nullable: true }
        keyword: { type: string, nullable: true }
        gclid: { type: string, nullable: true }
        device: { type: string, nullable: true }
        network: { type: string, nullable: true }
        country: { type: string, nullable: true }
        country_name: { type: string, nullable: true }
        landing_url: { type: string }
        referrer: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
    Click:
      type: object
      properties:
        id: { type: string, format: uuid }
        project_id: { type: string, nullable: true }
        visit_id: { type: string, nullable: true }
        click_id: { type: string }
        target_url: { type: string, nullable: true }
        element_type: { type: string, nullable: true }
        element_text: { type: string, nullable: true }
        campaign: { type: string, nullable: true }
        ad_group: { type: string, nullable: true }
        keyword: { type: string, nullable: true }
        device: { type: string, nullable: true }
        country: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
    Checkout:
      type: object
      properties:
        id: { type: string, format: uuid }
        project_id: { type: string, nullable: true }
        visit_id: { type: string, nullable: true }
        click_id: { type: string, nullable: true }
        status: { type: string }
        product_id: { type: string, nullable: true }
        offer_id: { type: string, nullable: true }
        currency: { type: string }
        amount: { type: string }
        checkout_id: { type: string }
        transaction_id: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
    Sale:
      type: object
      properties:
        id: { type: string, format: uuid }
        project_id: { type: string, nullable: true }
        visit_id: { type: string, nullable: true }
        click_id: { type: string, nullable: true }
        checkout_id: { type: string, nullable: true }
        sale_id: { type: string }
        status: { type: string }
        product_id: { type: string, nullable: true }
        offer_id: { type: string, nullable: true }
        amount: { type: string }
        commission: { type: string }
        currency: { type: string }
        transaction_id: { type: string, nullable: true }
        parent_sale_id: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
    Refund:
      type: object
      properties:
        id: { type: string, format: uuid }
        project_id: { type: string, nullable: true }
        refund_id: { type: string }
        sale_id: { type: string, nullable: true }
        status: { type: string }
        reason: { type: string, nullable: true }
        amount: { type: string }
        currency: { type: string }
        transaction_id: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
    Upsell:
      type: object
      properties:
        id: { type: string, format: uuid }
        project_id: { type: string, nullable: true }
        upsell_id: { type: string }
        sale_id: { type: string, nullable: true }
        parent_sale_id: { type: string, nullable: true }
        status: { type: string }
        offer_id: { type: string, nullable: true }
        amount: { type: string }
        commission: { type: string }
        currency: { type: string }
        transaction_id: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
            request_id: { type: string }
            details: { type: object, nullable: true }
    BlogArticleCreateRequest:
      type: object
      required: [title, excerpt, content]
      properties:
        title: { type: string, maxLength: 170 }
        excerpt: { type: string, maxLength: 320 }
        content: { type: string, minLength: 500 }
        category_slug: { type: string, nullable: true }
    BlogArticle:
      type: object
      properties:
        id: { type: string, format: uuid }
        title: { type: string }
        slug: { type: string }
        status: { type: string }
        is_user_generated: { type: boolean }
        author: { type: string }
        category: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
    BlogArticleCreateResponse:
      type: object
      properties:
        data: { $ref: "#/components/schemas/BlogArticle" }
        request_id: { type: string }
    ProjectListResponse:
      type: object
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/Project" }
        pagination: { $ref: "#/components/schemas/Pagination" }
        request_id: { type: string }
    ProjectDetailResponse:
      type: object
      properties:
        data: { $ref: "#/components/schemas/Project" }
        request_id: { type: string }
    VisitListResponse:
      type: object
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/Visit" }
        pagination: { $ref: "#/components/schemas/Pagination" }
        request_id: { type: string }
    VisitDetailResponse:
      type: object
      properties:
        data: { $ref: "#/components/schemas/Visit" }
        request_id: { type: string }
    ClickListResponse:
      type: object
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/Click" }
        pagination: { $ref: "#/components/schemas/Pagination" }
        request_id: { type: string }
    ClickDetailResponse:
      type: object
      properties:
        data: { $ref: "#/components/schemas/Click" }
        request_id: { type: string }
    CheckoutListResponse:
      type: object
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/Checkout" }
        pagination: { $ref: "#/components/schemas/Pagination" }
        request_id: { type: string }
    CheckoutDetailResponse:
      type: object
      properties:
        data: { $ref: "#/components/schemas/Checkout" }
        request_id: { type: string }
    SaleListResponse:
      type: object
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/Sale" }
        pagination: { $ref: "#/components/schemas/Pagination" }
        request_id: { type: string }
    SaleDetailResponse:
      type: object
      properties:
        data: { $ref: "#/components/schemas/Sale" }
        request_id: { type: string }
    RefundListResponse:
      type: object
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/Refund" }
        pagination: { $ref: "#/components/schemas/Pagination" }
        request_id: { type: string }
    RefundDetailResponse:
      type: object
      properties:
        data: { $ref: "#/components/schemas/Refund" }
        request_id: { type: string }
    UpsellListResponse:
      type: object
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/Upsell" }
        pagination: { $ref: "#/components/schemas/Pagination" }
        request_id: { type: string }
    UpsellDetailResponse:
      type: object
      properties:
        data: { $ref: "#/components/schemas/Upsell" }
        request_id: { type: string }
