openapi: 3.1.0
info:
  title: Varmply Backend API
  version: 0.1.0
  description: >
    Varmply API – MVP backend for campaigns, wallets, authentication, and
    influencer participation.
servers:
  - url: http://localhost:3000
    description: Local development server
  - url: https://api-staging.varmply.com
    description: Staging environment
  - url: https://api.varmply.com
    description: Production environment
tags:
  - name: Health
    description: Health check endpoints
  - name: Authentication
    description: User registration and authentication
  - name: Wallet
    description: Wallet balance and operations
  - name: Dev
    description: Development-only endpoints for testing (not available in production)
  - name: Campaigns
    description: Campaign creation, management, and funding
  - name: Participation
    description: Campaign participation and content submission
  - name: OAuth
    description: Social media OAuth connection flows
  - name: Metrics
    description: Content metrics and analytics endpoints
paths:
  /health:
    get:
      tags:
        - Health
      summary: Health check
      description: Returns the health status of the API service
      operationId: getHealthStatus
      responses:
        '200':
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                properties:
                  status:
                    type: string
                    example: ok
              example:
                status: ok
  /health/ready:
    get:
      tags:
        - Health
      summary: Readiness probe
      description: >-
        Checks if the API service is ready to serve traffic by verifying
        database connectivity. Returns 200 if healthy, 503 if the database is
        unavailable.
      operationId: getReadinessStatus
      responses:
        '200':
          description: Service is ready (database connected)
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                  - database
                properties:
                  status:
                    type: string
                    example: ok
                  database:
                    type: string
                    example: connected
              example:
                status: ok
                database: connected
        '503':
          description: Service is not ready (database unavailable)
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                  - database
                properties:
                  status:
                    type: string
                    example: error
                  database:
                    type: string
                    example: disconnected
                  error:
                    type: string
                    description: Error message describing the database connection issue
              example:
                status: error
                database: disconnected
                error: Database connection failed
  /auth/register:
    post:
      tags:
        - Authentication
      summary: Register a new user
      description: >-
        Register a new user account with email and password. Returns JWT tokens
        upon successful registration.
      operationId: registerUserWithEmailAndPassword
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - emailAddress
                - password
              properties:
                emailAddress:
                  type: string
                  format: email
                  description: Valid email address
                  example: founder@example.com
                password:
                  type: string
                  format: password
                  minLength: 8
                  description: Password must be at least 8 characters long
                  example: strongpassword123
            example:
              emailAddress: founder@example.com
              password: strongpassword123
      responses:
        '201':
          description: User registered successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - accessToken
                  - refreshToken
                properties:
                  accessToken:
                    type: string
                    description: JWT access token (expires in 15 minutes)
                    example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  refreshToken:
                    type: string
                    description: JWT refresh token (expires in 30 days)
                    example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
              example:
                accessToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                refreshToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
        '400':
          description: Validation error (e.g., invalid email format or password too short)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: Invalid email address or password format
                details:
                  fieldErrors:
                    emailAddress:
                      - Invalid email
        '409':
          description: Conflict - email address is already registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: CONFLICT
                message: An account already exists with this email address
  /auth/login:
    post:
      tags:
        - Authentication
      summary: Login with email and password
      description: Authenticate an existing user and receive JWT tokens
      operationId: loginUserWithEmailAndPassword
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - emailAddress
                - password
              properties:
                emailAddress:
                  type: string
                  format: email
                  description: Registered email address
                  example: founder@example.com
                password:
                  type: string
                  format: password
                  description: User password
                  example: strongpassword123
            example:
              emailAddress: founder@example.com
              password: strongpassword123
      responses:
        '200':
          description: Login successful
          content:
            application/json:
              schema:
                type: object
                required:
                  - accessToken
                  - refreshToken
                properties:
                  accessToken:
                    type: string
                    description: JWT access token (expires in 15 minutes)
                    example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  refreshToken:
                    type: string
                    description: JWT refresh token (expires in 30 days)
                    example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
              example:
                accessToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                refreshToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
        '400':
          description: Validation error or invalid request format
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: Invalid email address or password format
                details:
                  fieldErrors:
                    emailAddress:
                      - Invalid email
        '404':
          description: User not found or invalid credentials
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Invalid email address or password
  /wallet/balance:
    get:
      tags:
        - Wallet
      summary: Get wallet balance
      description: >-
        Retrieve the authenticated user's wallet balance for a specific
        currency. Returns 404 if the wallet does not exist. Requires a valid JWT
        access token.
      operationId: getWalletBalanceForAuthenticatedUser
      security:
        - bearerAuth: []
      parameters:
        - name: currency
          in: query
          required: true
          description: ISO currency code for the wallet
          schema:
            type: string
            enum:
              - NGN
              - USD
            example: NGN
      responses:
        '200':
          description: Wallet balance retrieved successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - currency
                  - balanceAmountCents
                properties:
                  currency:
                    type: string
                    description: ISO currency code
                    example: NGN
                  balanceAmountCents:
                    type: integer
                    description: Wallet balance in cents (smallest currency unit)
                    example: 0
              example:
                currency: NGN
                balanceAmountCents: 0
        '400':
          description: Validation error (e.g., missing or invalid currency parameter)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: >-
                  Currency query parameter is required and must be 'NGN' or
                  'USD'
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missingToken:
                  value:
                    error: UNAUTHORIZED
                    message: Missing authorization header
                invalidToken:
                  value:
                    error: UNAUTHORIZED
                    message: >-
                      Authorization header must be in the format 'Bearer
                      <token>'
                expiredToken:
                  value:
                    error: VALIDATION_ERROR
                    message: Token expired
        '404':
          description: Wallet not found for the user and currency
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Wallet not found for user
  /wallet:
    post:
      tags:
        - Wallet
      summary: Create a new wallet
      description: >-
        Create a new wallet for the authenticated user in a specific currency.
        Returns 201 Created on success, 409 Conflict if wallet already exists.
        Requires a valid JWT access token.
      operationId: createWalletForAuthenticatedUser
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - currencyCode
              properties:
                currencyCode:
                  type: string
                  enum:
                    - NGN
                    - USD
                  description: ISO currency code for the wallet
                  example: NGN
            example:
              currencyCode: NGN
      responses:
        '201':
          description: Wallet created successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - walletId
                  - currency
                  - balanceAmountCents
                properties:
                  walletId:
                    type: string
                    description: Unique identifier for the wallet
                    example: wallet_abc123
                  currency:
                    type: string
                    description: ISO currency code
                    example: NGN
                  balanceAmountCents:
                    type: integer
                    description: Initial wallet balance in cents (always 0 for new wallets)
                    example: 0
              example:
                walletId: wallet_abc123
                currency: NGN
                balanceAmountCents: 0
        '400':
          description: Validation error (e.g., invalid request body format)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: Invalid request body
                details:
                  fieldErrors:
                    currencyCode:
                      - Invalid enum value
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missingToken:
                  value:
                    error: UNAUTHORIZED
                    message: Missing authorization header
                invalidToken:
                  value:
                    error: UNAUTHORIZED
                    message: >-
                      Authorization header must be in the format 'Bearer
                      <token>'
                expiredToken:
                  value:
                    error: VALIDATION_ERROR
                    message: Token expired
        '409':
          description: Conflict - wallet already exists for this user and currency
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: CONFLICT
                message: Wallet already exists for user with currency NGN
  /dev/wallets/fund:
    post:
      tags:
        - Dev
      summary: Fund wallet (dev-only)
      description: >
        Fund a wallet for the authenticated user. This endpoint is only
        available in non-production environments.

        Returns 403 if called in production. Requires a valid JWT access token.

        The wallet must already exist for the user and currency (no
        auto-create).

        Creates a ledger-backed transaction to record the funding.
      operationId: fundWalletForDev
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - currencyCode
                - amountCents
              properties:
                currencyCode:
                  type: string
                  enum:
                    - NGN
                    - USD
                  description: ISO currency code for the wallet
                  example: NGN
                amountCents:
                  type: integer
                  minimum: 1
                  description: Amount to fund in cents (must be positive)
                  example: 500000
            example:
              currencyCode: NGN
              amountCents: 500000
      responses:
        '200':
          description: Wallet funded successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - walletId
                  - userId
                  - currencyCode
                  - availableBalanceAmountCents
                  - lockedBalanceAmountCents
                  - createdAtTimestamp
                  - lastUpdatedAtTimestamp
                properties:
                  walletId:
                    type: string
                    description: Unique identifier for the wallet
                    example: wallet_abc123
                  userId:
                    type: string
                    description: User identifier who owns the wallet
                    example: user_123
                  currencyCode:
                    type: string
                    enum:
                      - NGN
                      - USD
                    description: ISO currency code
                    example: NGN
                  availableBalanceAmountCents:
                    type: integer
                    description: Updated available balance in cents
                    example: 500000
                  lockedBalanceAmountCents:
                    type: integer
                    description: Locked balance in cents (unchanged)
                    example: 0
                  createdAtTimestamp:
                    type: string
                    format: date-time
                    description: ISO 8601 timestamp when the wallet was created
                    example: '2024-01-01T00:00:00Z'
                  lastUpdatedAtTimestamp:
                    type: string
                    format: date-time
                    description: ISO 8601 timestamp when the wallet was last updated
                    example: '2024-01-01T00:00:00Z'
              example:
                walletId: wallet_abc123
                userId: user_123
                currencyCode: NGN
                availableBalanceAmountCents: 500000
                lockedBalanceAmountCents: 0
                createdAtTimestamp: '2024-01-01T00:00:00Z'
                lastUpdatedAtTimestamp: '2024-01-01T00:00:00Z'
        '400':
          description: >-
            Validation error (e.g., invalid request body, negative amount,
            invalid currency)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: Invalid request body
                details:
                  fieldErrors:
                    amountCents:
                      - Number must be greater than 0
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missingToken:
                  value:
                    error: UNAUTHORIZED
                    message: Missing authorization header
                invalidToken:
                  value:
                    error: UNAUTHORIZED
                    message: >-
                      Authorization header must be in the format 'Bearer
                      <token>'
                expiredToken:
                  value:
                    error: VALIDATION_ERROR
                    message: Token expired
        '403':
          description: Permission denied - endpoint not available in production
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: PERMISSION_DENIED
                message: Dev wallet funding is not available in production
        '404':
          description: Wallet not found for the user and currency
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Wallet not found for user
  /campaigns:
    post:
      tags:
        - Campaigns
      summary: Create a new campaign
      description: >-
        Create a new campaign for the authenticated user. Returns 201 Created on
        success. Requires a valid JWT access token.
      operationId: createCampaignForAuthenticatedUser
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - campaignTitle
                - campaignDescription
                - campaignObjectiveType
                - campaignCurrencyCode
              properties:
                campaignTitle:
                  type: string
                  minLength: 3
                  description: Title of the campaign
                  example: Summer Product Launch
                campaignDescription:
                  type: string
                  minLength: 1
                  description: Description of the campaign
                  example: >-
                    Promote our new summer product line to increase brand
                    awareness
                campaignObjectiveType:
                  type: string
                  enum:
                    - CAMPAIGN_OBJECTIVE_AWARENESS
                    - CAMPAIGN_OBJECTIVE_ENGAGEMENT
                    - CAMPAIGN_OBJECTIVE_CONVERSIONS
                  description: Objective type for the campaign
                  example: CAMPAIGN_OBJECTIVE_AWARENESS
                campaignCurrencyCode:
                  type: string
                  enum:
                    - NGN
                    - USD
                  description: Currency code for the campaign
                  example: NGN
                targetBudgetAmountCents:
                  type: integer
                  minimum: 1
                  nullable: true
                  description: Target budget in cents (optional)
                  example: 1000000
                campaignStartDateTimestamp:
                  type: string
                  format: date-time
                  nullable: true
                  description: Campaign start date (optional)
                  example: '2024-06-01T00:00:00Z'
                campaignEndDateTimestamp:
                  type: string
                  format: date-time
                  nullable: true
                  description: Campaign end date (optional, must be after start date)
                  example: '2024-08-31T23:59:59Z'
            example:
              campaignTitle: Summer Product Launch
              campaignDescription: Promote our new summer product line to increase brand awareness
              campaignObjectiveType: CAMPAIGN_OBJECTIVE_AWARENESS
              campaignCurrencyCode: NGN
              targetBudgetAmountCents: 1000000
              campaignStartDateTimestamp: '2024-06-01T00:00:00Z'
              campaignEndDateTimestamp: '2024-08-31T23:59:59Z'
      responses:
        '201':
          description: Campaign created successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - campaign
                  - campaignEscrow
                properties:
                  campaign:
                    $ref: '#/components/schemas/Campaign'
                  campaignEscrow:
                    $ref: '#/components/schemas/CampaignEscrow'
              example:
                campaign:
                  campaignId: campaign_abc123
                  ownerUserId: user_123
                  campaignTitle: Summer Product Launch
                  campaignDescription: >-
                    Promote our new summer product line to increase brand
                    awareness
                  campaignObjectiveType: CAMPAIGN_OBJECTIVE_AWARENESS
                  campaignCurrencyCode: NGN
                  targetBudgetAmountCents: 1000000
                  campaignLifeCycleStatus: CAMPAIGN_DRAFT
                  campaignStartDateTimestamp: '2024-06-01T00:00:00Z'
                  campaignEndDateTimestamp: '2024-08-31T23:59:59Z'
                  createdAtTimestamp: '2024-01-01T00:00:00Z'
                  lastUpdatedAtTimestamp: '2024-01-01T00:00:00Z'
                campaignEscrow:
                  escrowId: escrow_xyz789
                  campaignId: campaign_abc123
                  escrowCurrencyCode: NGN
                  currentEscrowBalanceAmountCents: 0
                  totalFundedAmountCents: 0
                  totalReleasedAmountCents: 0
                  totalRefundedAmountCents: 0
                  createdAtTimestamp: '2024-01-01T00:00:00Z'
                  lastUpdatedAtTimestamp: '2024-01-01T00:00:00Z'
        '400':
          description: >-
            Validation error (e.g., invalid request body, end date before start
            date)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: invalid campaign creation payload
                details:
                  fieldErrors:
                    campaignTitle:
                      - String must contain at least 3 character(s)
                    campaignEndDateTimestamp:
                      - Campaign end date must be after start date
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /campaigns/{campaignId}:
    get:
      tags:
        - Campaigns
      summary: Get campaign details
      description: >-
        Retrieve a campaign summary for the authenticated user. Only the
        campaign owner can access the campaign. Returns 200 OK on success, 404
        if not found, 403 if not owned by user. Requires a valid JWT access
        token.
      operationId: getCampaignSummaryForAuthenticatedUser
      security:
        - bearerAuth: []
      parameters:
        - name: campaignId
          in: path
          required: true
          description: Unique identifier for the campaign
          schema:
            type: string
            example: campaign_abc123
      responses:
        '200':
          description: Campaign retrieved successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - campaign
                  - campaignEscrow
                properties:
                  campaign:
                    $ref: '#/components/schemas/Campaign'
                  campaignEscrow:
                    $ref: '#/components/schemas/CampaignEscrow'
              example:
                campaign:
                  campaignId: campaign_abc123
                  ownerUserId: user_123
                  campaignTitle: Summer Product Launch
                  campaignDescription: >-
                    Promote our new summer product line to increase brand
                    awareness
                  campaignObjectiveType: CAMPAIGN_OBJECTIVE_AWARENESS
                  campaignCurrencyCode: NGN
                  targetBudgetAmountCents: 1000000
                  campaignLifeCycleStatus: CAMPAIGN_ACTIVE
                  campaignStartDateTimestamp: '2024-06-01T00:00:00Z'
                  campaignEndDateTimestamp: '2024-08-31T23:59:59Z'
                  createdAtTimestamp: '2024-01-01T00:00:00Z'
                  lastUpdatedAtTimestamp: '2024-01-01T00:00:00Z'
                campaignEscrow:
                  escrowId: escrow_xyz789
                  campaignId: campaign_abc123
                  escrowCurrencyCode: NGN
                  currentEscrowBalanceAmountCents: 500000
                  totalFundedAmountCents: 500000
                  totalReleasedAmountCents: 0
                  totalRefundedAmountCents: 0
                  createdAtTimestamp: '2024-01-01T00:00:00Z'
                  lastUpdatedAtTimestamp: '2024-01-01T12:00:00Z'
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Permission denied - user is not the campaign owner
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: PERMISSION_ERROR
                message: You do not have permission to access this campaign
        '404':
          description: Campaign not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Campaign not found
  /campaigns/{campaignId}/summary:
    get:
      tags:
        - Campaigns
      summary: Get campaign analytics summary
      description: >-
        Retrieve a comprehensive analytics summary for a campaign including
        escrow, participation, submission, and payout metrics. Returns 200 OK on
        success, 404 if campaign not found. Requires a valid JWT access token.
      operationId: getCampaignAnalyticsSummary
      security:
        - bearerAuth: []
      parameters:
        - name: campaignId
          in: path
          required: true
          description: Unique identifier for the campaign
          schema:
            type: string
            example: campaign_abc123
      responses:
        '200':
          description: Campaign analytics summary retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignSummary'
              example:
                campaign:
                  campaignId: campaign_abc123
                  ownerUserId: user_123
                  campaignTitle: Summer Product Launch
                  campaignDescription: >-
                    Promote our new summer product line to increase brand
                    awareness
                  campaignObjectiveType: CAMPAIGN_OBJECTIVE_AWARENESS
                  campaignCurrencyCode: NGN
                  targetBudgetAmountCents: 1000000
                  campaignLifeCycleStatus: CAMPAIGN_ACTIVE
                  campaignStartDateTimestamp: '2024-06-01T00:00:00Z'
                  campaignEndDateTimestamp: '2024-08-31T23:59:59Z'
                  completedAtTimestamp: null
                  createdAtTimestamp: '2024-01-01T00:00:00Z'
                  lastUpdatedAtTimestamp: '2024-01-01T00:00:00Z'
                escrowSummary:
                  funded: 500000
                  released: 300000
                  refunded: 0
                  currentBalance: 200000
                participationCounts:
                  total: 10
                  active: 7
                  completed: 3
                submissionCounts:
                  total: 15
                  approved: 8
                  rejected: 2
                  pending: 5
                payoutSummary:
                  totalInfluencerPayout: 250000
                  totalPlatformFee: 50000
                  payoutCount: 5
                lifecycle:
                  status: CAMPAIGN_ACTIVE
                  completedAtTimestamp: null
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Campaign not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Campaign with ID campaign_abc123 not found
  /campaigns/{campaignId}/ledger:
    get:
      tags:
        - Campaigns
      summary: Get campaign ledger activity feed
      description: >-
        Retrieve campaign ledger transaction events as an activity feed, showing
        all financial transactions related to a campaign in chronological order
        (newest first). Returns 200 OK on success, 404 if campaign not found.
        Supports optional limit query parameter (default 50). Requires a valid
        JWT access token.
      operationId: getCampaignLedgerEvents
      security:
        - bearerAuth: []
      parameters:
        - name: campaignId
          in: path
          required: true
          description: Unique identifier for the campaign
          schema:
            type: string
            example: campaign_abc123
        - name: limit
          in: query
          required: false
          description: Maximum number of events to return (default 50)
          schema:
            type: integer
            minimum: 1
            default: 50
            example: 50
      responses:
        '200':
          description: Campaign ledger events retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CampaignLedgerEvent'
              example:
                - transactionId: ledger_tx_abc123
                  occurredAtTimestamp: '2024-01-15T10:30:00Z'
                  transactionType: LEDGER_ENTRY_TRANSACTION_TYPE_INFLUENCER_PAYOUT_GROSS
                  description: Influencer payout
                  totalAmountCents: 10000
                  currencyCode: NGN
                  fromAccounts:
                    - ledgerAccountType: LEDGER_ACCOUNT_TYPE_CAMPAIGN_ESCROW
                      ledgerAccountReferenceId: campaign_abc123
                      amountCents: 10000
                  toAccounts:
                    - ledgerAccountType: LEDGER_ACCOUNT_TYPE_USER_WALLET
                      ledgerAccountReferenceId: user_456
                      amountCents: 9000
                    - ledgerAccountType: LEDGER_ACCOUNT_TYPE_PLATFORM_WALLET
                      ledgerAccountReferenceId: PLATFORM_WALLET
                      amountCents: 1000
                - transactionId: ledger_tx_def456
                  occurredAtTimestamp: '2024-01-10T08:00:00Z'
                  transactionType: LEDGER_ENTRY_TRANSACTION_TYPE_ESCROW_FUNDING
                  description: Escrow funded
                  totalAmountCents: 50000
                  currencyCode: NGN
                  fromAccounts:
                    - ledgerAccountType: LEDGER_ACCOUNT_TYPE_USER_WALLET
                      ledgerAccountReferenceId: user_123
                      amountCents: 50000
                  toAccounts:
                    - ledgerAccountType: LEDGER_ACCOUNT_TYPE_CAMPAIGN_ESCROW
                      ledgerAccountReferenceId: campaign_abc123
                      amountCents: 50000
        '400':
          description: Validation error (e.g., invalid limit parameter)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: limit query parameter must be a positive integer
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Campaign not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Campaign with ID campaign_abc123 not found
  /campaigns/{campaignId}/timeline:
    get:
      tags:
        - Campaigns
      summary: Get campaign timeline analytics
      description: >-
        Retrieve time-series timeline data for a campaign with daily buckets
        showing cumulative payouts, escrow balance, participation counts, and
        approved submission counts over time. Returns 200 OK on success, 404 if
        campaign not found. Supports optional limitDays query parameter to limit
        the number of days returned. Requires a valid JWT access token.
      operationId: getCampaignTimeline
      security:
        - bearerAuth: []
      parameters:
        - name: campaignId
          in: path
          required: true
          description: Unique identifier for the campaign
          schema:
            type: string
            example: campaign_abc123
        - name: limitDays
          in: query
          required: false
          description: Maximum number of days to return (returns most recent N days)
          schema:
            type: integer
            minimum: 1
            example: 30
      responses:
        '200':
          description: Campaign timeline retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignTimeline'
              example:
                campaignId: campaign_abc123
                bucketGranularity: DAY
                points:
                  - bucketDate: '2024-01-01'
                    bucketStartTimestamp: '2024-01-01T00:00:00Z'
                    bucketEndTimestamp: '2024-01-01T23:59:59.999Z'
                    cumulativePayoutAmountCents: 0
                    remainingEscrowBalanceAmountCents: 0
                    totalParticipationCount: 0
                    totalApprovedSubmissionCount: 0
                  - bucketDate: '2024-01-02'
                    bucketStartTimestamp: '2024-01-02T00:00:00Z'
                    bucketEndTimestamp: '2024-01-02T23:59:59.999Z'
                    cumulativePayoutAmountCents: 0
                    remainingEscrowBalanceAmountCents: 50000
                    totalParticipationCount: 2
                    totalApprovedSubmissionCount: 0
                  - bucketDate: '2024-01-15'
                    bucketStartTimestamp: '2024-01-15T00:00:00Z'
                    bucketEndTimestamp: '2024-01-15T23:59:59.999Z'
                    cumulativePayoutAmountCents: 10000
                    remainingEscrowBalanceAmountCents: 40000
                    totalParticipationCount: 5
                    totalApprovedSubmissionCount: 3
        '400':
          description: Validation error (e.g., invalid limitDays parameter)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: limitDays query parameter must be a positive integer
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Campaign not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Campaign with ID campaign_abc123 not found
  /campaigns/{campaignId}/fund:
    post:
      tags:
        - Campaigns
      summary: Fund a campaign
      description: >-
        Fund a campaign from the authenticated user's wallet. Only the campaign
        owner can fund their campaign. Returns 200 OK on success. Requires a
        valid JWT access token.
      operationId: fundCampaignFromAuthenticatedUserWallet
      security:
        - bearerAuth: []
      parameters:
        - name: campaignId
          in: path
          required: true
          description: Unique identifier for the campaign
          schema:
            type: string
            example: campaign_abc123
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - fundingAmountCents
                - walletCurrencyCode
              properties:
                fundingAmountCents:
                  type: integer
                  minimum: 1
                  description: Amount to fund in cents
                  example: 500000
                walletCurrencyCode:
                  type: string
                  enum:
                    - NGN
                    - USD
                  description: Currency code of the wallet to fund from
                  example: NGN
            example:
              fundingAmountCents: 500000
              walletCurrencyCode: NGN
      responses:
        '200':
          description: Campaign funded successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - campaign
                  - campaignEscrow
                properties:
                  campaign:
                    $ref: '#/components/schemas/Campaign'
                  campaignEscrow:
                    $ref: '#/components/schemas/CampaignEscrow'
              example:
                campaign:
                  campaignId: campaign_abc123
                  ownerUserId: user_123
                  campaignTitle: Summer Product Launch
                  campaignDescription: Promote our new summer product line
                  campaignObjectiveType: CAMPAIGN_OBJECTIVE_AWARENESS
                  campaignCurrencyCode: NGN
                  targetBudgetAmountCents: 1000000
                  campaignLifeCycleStatus: CAMPAIGN_ACTIVE
                  campaignStartDateTimestamp: '2024-06-01T00:00:00Z'
                  campaignEndDateTimestamp: '2024-08-31T23:59:59Z'
                  completedAtTimestamp: null
                  createdAtTimestamp: '2024-01-01T00:00:00Z'
                  lastUpdatedAtTimestamp: '2024-01-01T00:00:00Z'
                campaignEscrow:
                  escrowId: escrow_xyz789
                  campaignId: campaign_abc123
                  escrowCurrencyCode: NGN
                  currentEscrowBalanceAmountCents: 500000
                  totalFundedAmountCents: 500000
                  totalReleasedAmountCents: 0
                  totalRefundedAmountCents: 0
                  createdAtTimestamp: '2024-01-01T00:00:00Z'
                  lastUpdatedAtTimestamp: '2024-01-01T12:00:00Z'
        '400':
          description: Validation error or funding failure
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ErrorResponse'
                  - type: object
                    required:
                      - error
                      - message
                    properties:
                      error:
                        type: string
                        enum:
                          - >-
                            CAMPAIGN_FUNDING_FAILED_DUE_TO_INSUFFICIENT_WALLET_BALANCE
                          - >-
                            CAMPAIGN_FUNDING_FAILED_DUE_TO_INVALID_CAMPAIGN_STATUS
                          - CAMPAIGN_FUNDING_FAILED_DUE_TO_CURRENCY_MISMATCH
                      message:
                        type: string
                      requiredAdditionalFundingAmountCents:
                        type: integer
                        description: Required when error is INSUFFICIENT_BALANCE
                      currentCampaignLifeCycleStatus:
                        type: string
                        description: Required when error is INVALID_STATUS
                      walletCurrencyCode:
                        type: string
                        description: Required when error is CURRENCY_MISMATCH
                      campaignCurrencyCode:
                        type: string
                        description: Required when error is CURRENCY_MISMATCH
              examples:
                validationError:
                  value:
                    error: VALIDATION_ERROR
                    message: invalid campaign funding payload
                insufficientBalance:
                  value:
                    error: CAMPAIGN_FUNDING_FAILED_DUE_TO_INSUFFICIENT_WALLET_BALANCE
                    message: Insufficient balance to fund campaign
                    requiredAdditionalFundingAmountCents: 200000
                invalidStatus:
                  value:
                    error: CAMPAIGN_FUNDING_FAILED_DUE_TO_INVALID_CAMPAIGN_STATUS
                    message: Campaign is not in a valid status to be funded
                    currentCampaignLifeCycleStatus: CAMPAIGN_COMPLETED
                currencyMismatch:
                  value:
                    error: CAMPAIGN_FUNDING_FAILED_DUE_TO_CURRENCY_MISMATCH
                    message: Wallet currency code does not match campaign currency code
                    walletCurrencyCode: USD
                    campaignCurrencyCode: NGN
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Permission denied - user is not the campaign owner
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: PERMISSION_ERROR
                message: You do not have permission to fund this campaign
        '404':
          description: Campaign not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Campaign not found
  /campaigns/{campaignId}/finalize:
    post:
      tags:
        - Campaigns
      summary: Finalize a campaign
      description: >
        Finalize a campaign if completion conditions are met. Only the campaign
        owner can finalize their campaign. Completion requires either: campaign
        endDate has passed OR escrow balance is zero. Returns 200 OK with
        updated campaign on success, 400 if not eligible. Requires a valid JWT
        access token.
      operationId: finalizeCampaign
      security:
        - bearerAuth: []
      parameters:
        - name: campaignId
          in: path
          required: true
          description: Unique identifier for the campaign
          schema:
            type: string
            example: campaign_abc123
      responses:
        '200':
          description: Campaign finalized successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - campaign
                  - campaignEscrow
                properties:
                  campaign:
                    $ref: '#/components/schemas/Campaign'
                  campaignEscrow:
                    $ref: '#/components/schemas/CampaignEscrow'
              example:
                campaign:
                  campaignId: campaign_abc123
                  ownerUserId: user_123
                  campaignTitle: Summer Product Launch
                  campaignDescription: >-
                    Promote our new summer product line to increase brand
                    awareness
                  campaignObjectiveType: CAMPAIGN_OBJECTIVE_AWARENESS
                  campaignCurrencyCode: NGN
                  targetBudgetAmountCents: 1000000
                  campaignLifeCycleStatus: CAMPAIGN_COMPLETED_WITH_REFUND
                  campaignStartDateTimestamp: '2024-06-01T00:00:00Z'
                  campaignEndDateTimestamp: '2024-08-31T23:59:59Z'
                  completedAtTimestamp: '2024-09-01T00:00:00Z'
                  createdAtTimestamp: '2024-01-01T00:00:00Z'
                  lastUpdatedAtTimestamp: '2024-09-01T00:00:00Z'
                campaignEscrow:
                  escrowId: escrow_xyz789
                  campaignId: campaign_abc123
                  escrowCurrencyCode: NGN
                  currentEscrowBalanceAmountCents: 0
                  totalFundedAmountCents: 500000
                  totalReleasedAmountCents: 300000
                  totalRefundedAmountCents: 200000
                  createdAtTimestamp: '2024-01-01T00:00:00Z'
                  lastUpdatedAtTimestamp: '2024-09-01T00:00:00Z'
        '400':
          description: Campaign is not yet eligible for completion
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                  - message
                  - campaign
                properties:
                  error:
                    type: string
                    example: VALIDATION_ERROR
                  message:
                    type: string
                    example: Campaign is not yet eligible for completion
                  campaign:
                    $ref: '#/components/schemas/Campaign'
              example:
                error: VALIDATION_ERROR
                message: >-
                  Campaign is not yet eligible for completion. Completion
                  requires either: endDate has passed OR escrow balance is zero.
                campaign:
                  campaignId: campaign_abc123
                  ownerUserId: user_123
                  campaignTitle: Summer Product Launch
                  campaignDescription: >-
                    Promote our new summer product line to increase brand
                    awareness
                  campaignObjectiveType: CAMPAIGN_OBJECTIVE_AWARENESS
                  campaignCurrencyCode: NGN
                  targetBudgetAmountCents: 1000000
                  campaignLifeCycleStatus: CAMPAIGN_ACTIVE
                  campaignStartDateTimestamp: '2024-06-01T00:00:00Z'
                  campaignEndDateTimestamp: '2024-12-31T23:59:59Z'
                  completedAtTimestamp: null
                  createdAtTimestamp: '2024-01-01T00:00:00Z'
                  lastUpdatedAtTimestamp: '2024-01-01T00:00:00Z'
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Permission denied - user is not the campaign owner
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: PERMISSION_ERROR
                message: You do not have permission to view this campaign
        '404':
          description: Campaign not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Campaign not found
  /campaigns/{campaignId}/participations:
    post:
      tags:
        - Participation
      summary: Join a campaign as an influencer
      description: >-
        Join a campaign as an influencer. Creates an auto-accepted
        participation. Requires a valid JWT access token.
      operationId: joinCampaignAsInfluencer
      security:
        - bearerAuth: []
      parameters:
        - name: campaignId
          in: path
          required: true
          description: Unique identifier for the campaign
          schema:
            type: string
            example: campaign_abc123
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties: {}
      responses:
        '201':
          description: Successfully joined the campaign
          content:
            application/json:
              schema:
                type: object
                required:
                  - participation
                properties:
                  participation:
                    $ref: '#/components/schemas/Participation'
              example:
                participation:
                  participationId: participation_xyz789
                  campaignId: campaign_abc123
                  influencerUserId: user_123
                  participationStatus: PARTICIPATION_STATUS_APPROVED
                  contentSubmissionList: []
                  createdAtTimestamp: '2024-01-01T00:00:00Z'
                  lastUpdatedAtTimestamp: '2024-01-01T00:00:00Z'
        '400':
          description: >-
            Validation error (e.g., campaign not joinable, already
            participating)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: Campaign is not in a joinable state for new participation
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Campaign not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Campaign not found
  /participations/{participationId}/content-submissions:
    post:
      tags:
        - Participation
      summary: Submit content for a participation
      description: >-
        Submit content (e.g., Instagram post, TikTok video) for a participation.
        Requires a valid JWT access token. Only the influencer who owns the
        participation can submit content.
      operationId: submitParticipationContent
      security:
        - bearerAuth: []
      parameters:
        - name: participationId
          in: path
          required: true
          description: Unique identifier for the participation
          schema:
            type: string
            example: participation_xyz789
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformType
                - contentUrl
              properties:
                platformType:
                  type: string
                  enum:
                    - INSTAGRAM
                    - TIKTOK
                    - FACEBOOK
                    - YOUTUBE
                  description: Platform where the content was posted
                  example: INSTAGRAM
                contentUrl:
                  type: string
                  format: uri
                  description: URL to the content
                  example: https://instagram.com/p/abc123
                postedAtTimestamp:
                  type: string
                  format: date-time
                  nullable: true
                  description: When the content was posted (optional)
                  example: '2024-01-01T12:00:00Z'
            example:
              platformType: INSTAGRAM
              contentUrl: https://instagram.com/p/abc123
              postedAtTimestamp: '2024-01-01T12:00:00Z'
      responses:
        '200':
          description: Content submitted successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - participation
                properties:
                  participation:
                    $ref: '#/components/schemas/Participation'
              example:
                participation:
                  participationId: participation_xyz789
                  campaignId: campaign_abc123
                  influencerUserId: user_123
                  participationStatus: PARTICIPATION_STATUS_CONTENT_SUBMITTED
                  contentSubmissionList:
                    - platformType: INSTAGRAM
                      contentUrl: https://instagram.com/p/abc123
                      submittedAtTimestamp: '2024-01-01T12:00:00Z'
                      postedAtTimestamp: '2024-01-01T12:00:00Z'
                  createdAtTimestamp: '2024-01-01T00:00:00Z'
                  lastUpdatedAtTimestamp: '2024-01-01T12:00:00Z'
        '400':
          description: >-
            Validation error (e.g., invalid URL, empty content URL,
            participation not in valid state)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: Content URL must be a non-empty string
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: >-
            Permission denied - user is not the influencer for this
            participation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: PERMISSION_ERROR
                message: You are not allowed to submit content for this participation
        '404':
          description: Participation not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Participation not found
  /participations/{participationId}/approve:
    post:
      tags:
        - Participation
      summary: Approve participation content
      description: >-
        Approve submitted content for a participation. Only the campaign owner
        can approve content. Requires a valid JWT access token.
      operationId: approveParticipationContent
      security:
        - bearerAuth: []
      parameters:
        - name: participationId
          in: path
          required: true
          description: Unique identifier for the participation
          schema:
            type: string
            example: participation_xyz789
      responses:
        '200':
          description: Content approved successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - participation
                properties:
                  participation:
                    $ref: '#/components/schemas/Participation'
              example:
                participation:
                  participationId: participation_xyz789
                  campaignId: campaign_abc123
                  influencerUserId: user_123
                  participationStatus: PARTICIPATION_STATUS_APPROVED
                  contentSubmissionList:
                    - platformType: INSTAGRAM
                      contentUrl: https://instagram.com/p/abc123
                      submittedAtTimestamp: '2024-01-01T12:00:00Z'
                      postedAtTimestamp: '2024-01-01T12:00:00Z'
                  createdAtTimestamp: '2024-01-01T00:00:00Z'
                  lastUpdatedAtTimestamp: '2024-01-01T13:00:00Z'
        '400':
          description: >-
            Validation error (e.g., no submitted content, participation not in
            valid state)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: Only participations with submitted content can be approved
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Permission denied - user is not the campaign owner
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: PERMISSION_ERROR
                message: You do not have permission to approve this participation
        '404':
          description: Participation not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Participation not found
  /oauth/instagram/start:
    post:
      tags:
        - OAuth
      summary: Start Instagram OAuth connect flow
      description: >
        Initiates the Instagram OAuth connect flow for an authenticated user.

        Returns an authorization URL that the client should redirect the user
        to.

        The user will authorize the app on Instagram and be redirected back to
        the callback endpoint.
      operationId: startInstagramOAuth
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - redirectPath
              properties:
                redirectPath:
                  type: string
                  description: >-
                    Relative path (e.g., "/dashboard") where the user should be
                    redirected after OAuth completion
                  example: /dashboard
                  pattern: ^/
            example:
              redirectPath: /dashboard
      responses:
        '200':
          description: Authorization URL generated successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - authorizationUrl
                properties:
                  authorizationUrl:
                    type: string
                    format: uri
                    description: URL to redirect the user to for Instagram authorization
                    example: >-
                      https://api.instagram.com/oauth/authorize?client_id=xxx&redirect_uri=xxx&scope=xxx&response_type=code&state=xxx
              example:
                authorizationUrl: >-
                  https://api.instagram.com/oauth/authorize?client_id=xxx&redirect_uri=xxx&scope=xxx&response_type=code&state=xxx
        '400':
          description: Validation error (e.g., invalid redirect path)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: Redirect path must be a relative path starting with '/'
                timestamp: '2024-01-01T00:00:00Z'
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: UNAUTHORIZED
                message: Missing or invalid authorization token
                timestamp: '2024-01-01T00:00:00Z'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: INTERNAL_SERVER_ERROR
                message: An unexpected error occurred
                timestamp: '2024-01-01T00:00:00Z'
  /oauth/instagram/callback:
    get:
      tags:
        - OAuth
      summary: Handle Instagram OAuth callback
      description: >
        Handles the OAuth callback from Instagram after user authorization.

        Exchanges the authorization code for access tokens, fetches user
        profile,

        and creates/updates the social account. Redirects to the redirect URI

        specified in the original request with a status query parameter.
      operationId: handleInstagramOAuthCallback
      parameters:
        - name: code
          in: query
          required: true
          description: Authorization code returned by Instagram
          schema:
            type: string
          example: AQBx...
        - name: state
          in: query
          required: true
          description: State token for CSRF protection and context
          schema:
            type: string
          example: eyJhbGc...
      responses:
        '302':
          description: Redirect to the redirect URI with status query parameter
          headers:
            Location:
              schema:
                type: string
                format: uri
              description: Redirect URI with status=success or status=error
              example: https://app.example.com/dashboard?status=success
        '400':
          description: Validation error (e.g., invalid state token or authorization code)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: Invalid state token or authorization code
                timestamp: '2024-01-01T00:00:00Z'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: INTERNAL_SERVER_ERROR
                message: An unexpected error occurred during OAuth callback processing
                timestamp: '2024-01-01T00:00:00Z'
  /participations/{participationId}/complete:
    post:
      tags:
        - Participation
      summary: Mark participation as completed
      description: >-
        Mark a participation as completed. Only the campaign owner can mark
        participations as completed. Requires a valid JWT access token.
      operationId: markParticipationCompleted
      security:
        - bearerAuth: []
      parameters:
        - name: participationId
          in: path
          required: true
          description: Unique identifier for the participation
          schema:
            type: string
            example: participation_xyz789
      responses:
        '200':
          description: Participation marked as completed successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - participation
                properties:
                  participation:
                    $ref: '#/components/schemas/Participation'
              example:
                participation:
                  participationId: participation_xyz789
                  campaignId: campaign_abc123
                  influencerUserId: user_123
                  participationStatus: PARTICIPATION_STATUS_COMPLETED
                  contentSubmissionList:
                    - platformType: INSTAGRAM
                      contentUrl: https://instagram.com/p/abc123
                      submittedAtTimestamp: '2024-01-01T12:00:00Z'
                      postedAtTimestamp: '2024-01-01T12:00:00Z'
                  createdAtTimestamp: '2024-01-01T00:00:00Z'
                  lastUpdatedAtTimestamp: '2024-01-01T14:00:00Z'
        '400':
          description: Validation error (e.g., participation not in approved state)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: Only approved participations can be marked as completed
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Permission denied - user is not the campaign owner
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: PERMISSION_ERROR
                message: >-
                  You do not have permission to mark this participation as
                  completed
        '404':
          description: Participation not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Participation not found
  /metrics/participations/{participationId}/refresh:
    post:
      tags:
        - Metrics
      summary: Refresh metrics for a participation's submitted content
      description: >
        Refreshes metrics (impressions, reach, likes, comments, saves, shares)
        for a participation's submitted content.

        Fetches the latest metrics from the social media platform API and
        creates a snapshot.

        Requires the participation to have submitted content and the user to
        have a connected social account with valid OAuth tokens.

        Returns 200 OK with the created metrics snapshot on success.
      operationId: refreshParticipationMetrics
      security:
        - bearerAuth: []
      parameters:
        - name: participationId
          in: path
          required: true
          description: Unique identifier for the participation
          schema:
            type: string
            example: participation_xyz789
      responses:
        '200':
          description: Metrics refreshed successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - snapshot
                properties:
                  snapshot:
                    $ref: '#/components/schemas/SubmissionMetricsSnapshot'
              example:
                snapshot:
                  snapshotId: snapshot_abc123
                  participationId: participation_xyz789
                  externalMediaId: ABC123
                  impressions: 1000
                  reach: 850
                  likes: 100
                  comments: 20
                  saves: 50
                  shares: 10
                  fetchedAtTimestamp: '2024-01-01T12:00:00Z'
                  createdAtTimestamp: '2024-01-01T12:00:00Z'
                  lastUpdatedAtTimestamp: '2024-01-01T12:00:00Z'
        '400':
          description: >-
            Validation error (e.g., no submitted content, invalid content URL,
            failed to fetch metrics)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: Could not extract media ID from content URL
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: UNAUTHORIZED
                message: Missing authorization header
        '404':
          description: >-
            Participation not found or user does not have a connected social
            account
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Participation not found
        '503':
          description: Service unavailable - submission metrics service is not available
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: SERVICE_UNAVAILABLE
                message: Submission metrics service is not available
  /metrics/campaigns/{campaignId}/refresh:
    post:
      tags:
        - Metrics
      summary: Refresh metrics for all eligible participations in a campaign
      description: >
        Refreshes metrics for all eligible participations in a campaign.

        Eligible participations are those with Instagram content submissions and
        status APPROVED or COMPLETED.

        Processes participations sequentially and returns a summary of
        successful and failed refreshes.

        Only the campaign owner can refresh metrics for their campaign.

        Returns 200 OK with the refresh summary on success.
      operationId: refreshCampaignMetrics
      security:
        - bearerAuth: []
      parameters:
        - name: campaignId
          in: path
          required: true
          description: Unique identifier for the campaign
          schema:
            type: string
            example: campaign_abc123
      responses:
        '200':
          description: Campaign metrics refresh completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignMetricsIngestionResult'
              example:
                campaignId: campaign_abc123
                totalEligibleParticipationCount: 5
                successfulCount: 4
                failedCount: 1
                failedParticipationIdList:
                  - participation_xyz789
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: VALIDATION_ERROR
                message: Invalid campaign ID format
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: UNAUTHORIZED
                message: Missing authorization header
        '403':
          description: Permission denied - user is not the campaign owner
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: PERMISSION_DENIED
                message: >-
                  You do not have permission to refresh metrics for this
                  campaign
        '404':
          description: Campaign not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Campaign with ID campaign_abc123 not found
        '503':
          description: >-
            Service unavailable - campaign metrics ingestion service is not
            available
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: SERVICE_UNAVAILABLE
                message: Campaign metrics ingestion service is not available
  /analytics/campaigns/{campaignId}/summary:
    get:
      tags:
        - Metrics
      summary: Get campaign analytics summary
      description: >
        Returns read-only analytics summary for a campaign, including aggregated
        metrics totals

        and participation-level breakdown. Only the campaign owner can view
        analytics.

        Returns aggregated metrics from the latest snapshot per participation.
      operationId: getCampaignAnalyticsSummary
      security:
        - bearerAuth: []
      parameters:
        - name: campaignId
          in: path
          required: true
          description: Unique identifier for the campaign
          schema:
            type: string
            example: campaign_abc123
      responses:
        '200':
          description: Campaign analytics summary
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignAnalyticsResult'
              example:
                summary:
                  campaignId: campaign_abc123
                  totalImpressions: 2592
                  totalReach: 2023
                  totalLikes: 192
                  totalComments: 112
                  totalSaves: 24
                  totalShares: 5
                  participationCountWithMetrics: 3
                  totalParticipationCount: 5
                  metricsCoverageRatio: 0.6
                  earliestSnapshotFetchedAtTimestamp: '2025-11-26T23:39:30.449Z'
                  latestSnapshotFetchedAtTimestamp: '2025-11-27T10:12:02.100Z'
                participationAnalyticsList:
                  - participationId: participation_xyz789
                    influencerUserId: user_123
                    externalMediaId: abc123
                    impressions: 2592
                    reach: 2023
                    likes: 192
                    comments: 112
                    saves: 24
                    shares: 5
                    lastMetricsSyncedAtTimestamp: '2025-11-26T23:39:30.449Z'
                    latestSnapshotFetchedAtTimestamp: '2025-11-26T23:39:30.449Z'
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: UNAUTHORIZED
                message: Missing authorization header
        '403':
          description: Permission denied - user is not the campaign owner
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: PERMISSION_DENIED
                message: You do not have permission to view analytics for this campaign
        '404':
          description: Campaign not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Campaign with ID campaign_abc123 not found
        '503':
          description: Service unavailable - campaign analytics service is not available
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: SERVICE_UNAVAILABLE
                message: Campaign analytics service is not available
  /analytics/campaigns/{campaignId}/payout-preview:
    get:
      tags:
        - Metrics
      summary: Get campaign payout preview
      description: >
        Returns a read-only payout preview for a campaign showing calculated
        payouts per participation

        based on metrics and a default payout formula, compared against
        available escrow balance.

        Only the campaign owner can view payout previews. This endpoint does not
        execute any payouts.
      operationId: getCampaignPayoutPreview
      security:
        - bearerAuth: []
      parameters:
        - name: campaignId
          in: path
          required: true
          description: Unique identifier for the campaign
          schema:
            type: string
            example: campaign_abc123
      responses:
        '200':
          description: Campaign payout preview
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignPayoutPreview'
              example:
                campaignId: campaign_abc123
                campaignCurrencyCode: NGN
                escrowAvailableAmountCents: 5000000
                totalRequestedPayoutAmountCents: 3200000
                shortfallAmountCents: 0
                participationPayoutList:
                  - participationId: participation_xyz789
                    influencerUserId: user_123
                    externalMediaId: abc123
                    impressions: 2592
                    reach: 2023
                    likes: 192
                    comments: 112
                    saves: 24
                    shares: 5
                    lastMetricsSyncedAtTimestamp: '2025-11-27T08:30:49.957Z'
                    latestSnapshotFetchedAtTimestamp: '2025-11-27T08:30:49.957Z'
                    grossPayoutAmountCents: 80000
        '401':
          description: Missing or invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: UNAUTHORIZED
                message: Missing authorization header
        '403':
          description: Permission denied - user is not the campaign owner
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: PERMISSION_DENIED
                message: >-
                  You do not have permission to view payout preview for this
                  campaign
        '404':
          description: Campaign or escrow not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: NOT_FOUND
                message: Campaign with ID campaign_abc123 not found
        '503':
          description: >-
            Service unavailable - campaign payout preview service is not
            available
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: SERVICE_UNAVAILABLE
                message: Campaign payout preview service is not available
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT access token obtained from /auth/register or /auth/login
  schemas:
    ErrorResponse:
      type: object
      required:
        - error
        - message
        - timestamp
      properties:
        error:
          type: string
          description: Error code
          example: VALIDATION_ERROR
        message:
          type: string
          description: Human-readable error message
          example: Invalid email address or password format
        details:
          type: object
          description: Additional error details (optional)
          additionalProperties: true
        requestId:
          type: string
          format: uuid
          description: >-
            Unique request identifier for tracing (included in X-Request-ID
            header)
          example: 550e8400-e29b-41d4-a716-446655440000
        timestamp:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the error occurred
          example: '2024-01-01T00:00:00Z'
    Participation:
      type: object
      required:
        - participationId
        - campaignId
        - influencerUserId
        - participationStatus
        - contentSubmissionList
        - createdAtTimestamp
        - lastUpdatedAtTimestamp
      properties:
        participationId:
          type: string
          description: Unique identifier for the participation
          example: participation_xyz789
        campaignId:
          type: string
          description: Unique identifier for the campaign
          example: campaign_abc123
        influencerUserId:
          type: string
          description: Unique identifier for the influencer user
          example: user_123
        participationStatus:
          type: string
          enum:
            - PARTICIPATION_STATUS_PENDING
            - PARTICIPATION_STATUS_APPROVED
            - PARTICIPATION_STATUS_REJECTED
            - PARTICIPATION_STATUS_CONTENT_SUBMITTED
            - PARTICIPATION_STATUS_COMPLETED
          description: Current status of the participation
          example: PARTICIPATION_STATUS_APPROVED
        contentSubmissionList:
          type: array
          description: List of content submissions for this participation
          items:
            $ref: '#/components/schemas/ParticipationContentSubmission'
        createdAtTimestamp:
          type: string
          format: date-time
          description: When the participation was created
          example: '2024-01-01T00:00:00Z'
        lastUpdatedAtTimestamp:
          type: string
          format: date-time
          description: When the participation was last updated
          example: '2024-01-01T00:00:00Z'
    ParticipationContentSubmission:
      type: object
      required:
        - platformType
        - contentUrl
        - submittedAtTimestamp
        - postedAtTimestamp
      properties:
        platformType:
          type: string
          enum:
            - INSTAGRAM
            - TIKTOK
            - FACEBOOK
            - YOUTUBE
          description: Platform where the content was posted
          example: INSTAGRAM
        contentUrl:
          type: string
          format: uri
          description: URL to the content
          example: https://instagram.com/p/abc123
        submittedAtTimestamp:
          type: string
          format: date-time
          description: When the content was submitted
          example: '2024-01-01T12:00:00Z'
        postedAtTimestamp:
          type: string
          format: date-time
          nullable: true
          description: When the content was posted (null if not yet posted)
          example: '2024-01-01T12:00:00Z'
    Campaign:
      type: object
      required:
        - campaignId
        - ownerUserId
        - campaignTitle
        - campaignDescription
        - campaignObjectiveType
        - campaignCurrencyCode
        - targetBudgetAmountCents
        - campaignLifeCycleStatus
        - campaignStartDateTimestamp
        - campaignEndDateTimestamp
        - completedAtTimestamp
        - createdAtTimestamp
        - lastUpdatedAtTimestamp
      properties:
        campaignId:
          type: string
          description: Unique identifier for the campaign
          example: campaign_abc123
        ownerUserId:
          type: string
          description: Unique identifier for the campaign owner
          example: user_123
        campaignTitle:
          type: string
          description: Title of the campaign
          example: Summer Product Launch
        campaignDescription:
          type: string
          description: Description of the campaign
          example: Promote our new summer product line to increase brand awareness
        campaignObjectiveType:
          type: string
          enum:
            - CAMPAIGN_OBJECTIVE_AWARENESS
            - CAMPAIGN_OBJECTIVE_ENGAGEMENT
            - CAMPAIGN_OBJECTIVE_CONVERSIONS
          description: Objective type for the campaign
          example: CAMPAIGN_OBJECTIVE_AWARENESS
        campaignCurrencyCode:
          type: string
          enum:
            - NGN
            - USD
          description: Currency code for the campaign
          example: NGN
        targetBudgetAmountCents:
          type: integer
          nullable: true
          description: Target budget in cents (null if no target set)
          example: 1000000
        campaignLifeCycleStatus:
          type: string
          enum:
            - CAMPAIGN_DRAFT
            - CAMPAIGN_ACTIVE
            - CAMPAIGN_PAUSED
            - CAMPAIGN_COMPLETED
            - CAMPAIGN_COMPLETED_WITH_REFUND
            - CAMPAIGN_CANCELLED
          description: Current lifecycle status of the campaign
          example: CAMPAIGN_ACTIVE
        campaignStartDateTimestamp:
          type: string
          format: date-time
          nullable: true
          description: Campaign start date (null if not set)
          example: '2024-06-01T00:00:00Z'
        campaignEndDateTimestamp:
          type: string
          format: date-time
          nullable: true
          description: Campaign end date (null if not set)
          example: '2024-08-31T23:59:59Z'
        completedAtTimestamp:
          type: string
          format: date-time
          nullable: true
          description: When the campaign was completed (null if not yet completed)
          example: '2024-09-01T00:00:00Z'
        createdAtTimestamp:
          type: string
          format: date-time
          description: When the campaign was created
          example: '2024-01-01T00:00:00Z'
        lastUpdatedAtTimestamp:
          type: string
          format: date-time
          description: When the campaign was last updated
          example: '2024-01-01T00:00:00Z'
    CampaignEscrow:
      type: object
      required:
        - escrowId
        - campaignId
        - escrowCurrencyCode
        - currentEscrowBalanceAmountCents
        - totalFundedAmountCents
        - totalReleasedAmountCents
        - totalRefundedAmountCents
        - createdAtTimestamp
        - lastUpdatedAtTimestamp
      properties:
        escrowId:
          type: string
          description: Unique identifier for the escrow
          example: escrow_xyz789
        campaignId:
          type: string
          description: Unique identifier for the associated campaign
          example: campaign_abc123
        escrowCurrencyCode:
          type: string
          enum:
            - NGN
            - USD
          description: Currency code for the escrow
          example: NGN
        currentEscrowBalanceAmountCents:
          type: integer
          description: Current balance in the escrow account (cents)
          example: 500000
        totalFundedAmountCents:
          type: integer
          description: Total amount funded into the escrow (cents)
          example: 500000
        totalReleasedAmountCents:
          type: integer
          description: Total amount released from the escrow (cents)
          example: 0
        totalRefundedAmountCents:
          type: integer
          description: Total amount refunded from the escrow (cents)
          example: 0
        createdAtTimestamp:
          type: string
          format: date-time
          description: When the escrow was created
          example: '2024-01-01T00:00:00Z'
        lastUpdatedAtTimestamp:
          type: string
          format: date-time
          description: When the escrow was last updated
          example: '2024-01-01T12:00:00Z'
    CampaignSummary:
      type: object
      required:
        - campaign
        - escrowSummary
        - participationCounts
        - submissionCounts
        - payoutSummary
        - lifecycle
      properties:
        campaign:
          $ref: '#/components/schemas/Campaign'
        escrowSummary:
          type: object
          required:
            - funded
            - released
            - refunded
            - currentBalance
          properties:
            funded:
              type: integer
              description: Total amount funded into escrow (cents)
              example: 500000
            released:
              type: integer
              description: Total amount released from escrow (cents)
              example: 300000
            refunded:
              type: integer
              description: Total amount refunded from escrow (cents)
              example: 0
            currentBalance:
              type: integer
              description: Current escrow balance (cents)
              example: 200000
        participationCounts:
          type: object
          required:
            - total
            - active
            - completed
          properties:
            total:
              type: integer
              description: Total number of participations
              example: 10
            active:
              type: integer
              description: Number of active participations (approved or content submitted)
              example: 7
            completed:
              type: integer
              description: Number of completed participations
              example: 3
        submissionCounts:
          type: object
          required:
            - total
            - approved
            - rejected
            - pending
          properties:
            total:
              type: integer
              description: Total number of content submissions
              example: 15
            approved:
              type: integer
              description: Number of approved submissions
              example: 8
            rejected:
              type: integer
              description: Number of rejected submissions
              example: 2
            pending:
              type: integer
              description: Number of pending submissions
              example: 5
        payoutSummary:
          type: object
          required:
            - totalInfluencerPayout
            - totalPlatformFee
            - payoutCount
          properties:
            totalInfluencerPayout:
              type: integer
              description: Total amount paid out to influencers (cents)
              example: 250000
            totalPlatformFee:
              type: integer
              description: Total platform fees collected (cents)
              example: 50000
            payoutCount:
              type: integer
              description: Number of payout transactions processed
              example: 5
        lifecycle:
          type: object
          required:
            - status
            - completedAtTimestamp
          properties:
            status:
              type: string
              enum:
                - CAMPAIGN_DRAFT
                - CAMPAIGN_ACTIVE
                - CAMPAIGN_PAUSED
                - CAMPAIGN_COMPLETED
                - CAMPAIGN_COMPLETED_WITH_REFUND
                - CAMPAIGN_CANCELLED
              description: Current lifecycle status of the campaign
              example: CAMPAIGN_ACTIVE
            completedAtTimestamp:
              type: string
              format: date-time
              nullable: true
              description: When the campaign was completed (null if not yet completed)
              example: null
    CampaignLedgerEvent:
      type: object
      required:
        - transactionId
        - occurredAtTimestamp
        - transactionType
        - description
        - totalAmountCents
        - currencyCode
        - fromAccounts
        - toAccounts
      properties:
        transactionId:
          type: string
          description: Unique identifier for the ledger transaction
          example: ledger_tx_abc123
        occurredAtTimestamp:
          type: string
          format: date-time
          description: When the transaction occurred
          example: '2024-01-15T10:30:00Z'
        transactionType:
          type: string
          enum:
            - LEDGER_ENTRY_TRANSACTION_TYPE_ESCROW_FUNDING
            - LEDGER_ENTRY_TRANSACTION_TYPE_PLATFORM_PAYOUT_FEE
            - LEDGER_ENTRY_TRANSACTION_TYPE_INFLUENCER_PAYOUT_NET
            - LEDGER_ENTRY_TRANSACTION_TYPE_INFLUENCER_PAYOUT_GROSS
            - LEDGER_ENTRY_TRANSACTION_TYPE_ADMIN_WALLET_FUNDING
            - LEDGER_ENTRY_TRANSACTION_TYPE_ESCROW_REFUND
            - LEDGER_ENTRY_TRANSACTION_TYPE_DEV_WALLET_FUND
          description: Type of ledger transaction
          example: LEDGER_ENTRY_TRANSACTION_TYPE_INFLUENCER_PAYOUT_GROSS
        description:
          type: string
          description: Human-friendly description of the transaction
          example: Influencer payout
        totalAmountCents:
          type: integer
          description: Sum of positive line amounts in cents
          example: 10000
        currencyCode:
          type: string
          enum:
            - NGN
            - USD
          description: Currency code for the transaction
          example: NGN
        fromAccounts:
          type: array
          description: Ledger lines with negative amounts (sources)
          items:
            $ref: '#/components/schemas/CampaignLedgerEventAccount'
        toAccounts:
          type: array
          description: Ledger lines with positive amounts (destinations)
          items:
            $ref: '#/components/schemas/CampaignLedgerEventAccount'
    CampaignLedgerEventAccount:
      type: object
      required:
        - ledgerAccountType
        - ledgerAccountReferenceId
        - amountCents
      properties:
        ledgerAccountType:
          type: string
          enum:
            - LEDGER_ACCOUNT_TYPE_USER_WALLET
            - LEDGER_ACCOUNT_TYPE_CAMPAIGN_ESCROW
            - LEDGER_ACCOUNT_TYPE_PLATFORM_WALLET
          description: Type of ledger account
          example: LEDGER_ACCOUNT_TYPE_USER_WALLET
        ledgerAccountReferenceId:
          type: string
          description: >-
            Reference identifier for the account (user ID, campaign ID, or
            PLATFORM_WALLET)
          example: user_456
        amountCents:
          type: integer
          description: Amount in cents (absolute value for fromAccounts)
          example: 10000
    CampaignTimeline:
      type: object
      required:
        - campaignId
        - bucketGranularity
        - points
      properties:
        campaignId:
          type: string
          description: Unique identifier for the campaign
          example: campaign_abc123
        bucketGranularity:
          type: string
          enum:
            - DAY
          description: Granularity of time buckets
          example: DAY
        points:
          type: array
          description: Timeline data points, one per day
          items:
            $ref: '#/components/schemas/CampaignTimelinePoint'
    CampaignTimelinePoint:
      type: object
      required:
        - bucketDate
        - bucketStartTimestamp
        - bucketEndTimestamp
        - cumulativePayoutAmountCents
        - remainingEscrowBalanceAmountCents
        - totalParticipationCount
        - totalApprovedSubmissionCount
      properties:
        bucketDate:
          type: string
          format: date
          description: Date of the bucket in YYYY-MM-DD format
          example: '2024-01-15'
        bucketStartTimestamp:
          type: string
          format: date-time
          description: Start of the bucket (00:00:00)
          example: '2024-01-15T00:00:00Z'
        bucketEndTimestamp:
          type: string
          format: date-time
          description: End of the bucket (23:59:59.999)
          example: '2024-01-15T23:59:59.999Z'
        cumulativePayoutAmountCents:
          type: integer
          description: Cumulative total of all payouts up to this bucket
          example: 10000
        remainingEscrowBalanceAmountCents:
          type: integer
          description: Remaining escrow balance at the end of this bucket
          example: 40000
        totalParticipationCount:
          type: integer
          description: Cumulative total of participations up to this bucket
          example: 5
        totalApprovedSubmissionCount:
          type: integer
          description: Cumulative total of approved submissions up to this bucket
          example: 3
    SubmissionMetricsSnapshot:
      type: object
      required:
        - snapshotId
        - participationId
        - externalMediaId
        - impressions
        - reach
        - likes
        - comments
        - saves
        - shares
        - fetchedAtTimestamp
        - createdAtTimestamp
        - lastUpdatedAtTimestamp
      properties:
        snapshotId:
          type: string
          description: Unique identifier for the metrics snapshot
          example: snapshot_abc123
        participationId:
          type: string
          description: Unique identifier for the participation
          example: participation_xyz789
        externalMediaId:
          type: string
          description: External media ID from the social platform (e.g., Instagram post ID)
          example: ABC123
        impressions:
          type: integer
          description: Number of impressions
          example: 1000
        reach:
          type: integer
          description: Number of unique users reached
          example: 850
        likes:
          type: integer
          description: Number of likes
          example: 100
        comments:
          type: integer
          description: Number of comments
          example: 20
        saves:
          type: integer
          description: Number of saves
          example: 50
        shares:
          type: integer
          description: Number of shares
          example: 10
        fetchedAtTimestamp:
          type: string
          format: date-time
          description: When the metrics were fetched from the platform API
          example: '2024-01-01T12:00:00Z'
        createdAtTimestamp:
          type: string
          format: date-time
          description: When the snapshot was created
          example: '2024-01-01T12:00:00Z'
        lastUpdatedAtTimestamp:
          type: string
          format: date-time
          description: When the snapshot was last updated
          example: '2024-01-01T12:00:00Z'
    CampaignMetricsIngestionResult:
      type: object
      required:
        - campaignId
        - totalEligibleParticipationCount
        - successfulCount
        - failedCount
        - failedParticipationIdList
      properties:
        campaignId:
          type: string
          description: Unique identifier for the campaign
          example: campaign_abc123
        totalEligibleParticipationCount:
          type: integer
          description: >-
            Total number of participations eligible for metrics refresh (has
            Instagram submission and status APPROVED or COMPLETED)
          example: 5
        successfulCount:
          type: integer
          description: Number of participations that were successfully refreshed
          example: 4
        failedCount:
          type: integer
          description: Number of participations that failed to refresh
          example: 1
        failedParticipationIdList:
          type: array
          description: List of participation IDs that failed to refresh
          items:
            type: string
          example:
            - participation_xyz789
    CampaignAnalyticsResult:
      type: object
      required:
        - summary
        - participationAnalyticsList
      properties:
        summary:
          $ref: '#/components/schemas/CampaignAnalyticsSummary'
        participationAnalyticsList:
          type: array
          description: >-
            List of participation analytics views, one per participation that
            has metrics
          items:
            $ref: '#/components/schemas/ParticipationAnalyticsView'
    CampaignAnalyticsSummary:
      type: object
      required:
        - campaignId
        - totalImpressions
        - totalReach
        - totalLikes
        - totalComments
        - totalSaves
        - totalShares
        - participationCountWithMetrics
        - totalParticipationCount
        - metricsCoverageRatio
        - earliestSnapshotFetchedAtTimestamp
        - latestSnapshotFetchedAtTimestamp
      properties:
        campaignId:
          type: string
          description: Unique identifier for the campaign
          example: campaign_abc123
        totalImpressions:
          type: integer
          description: Sum of impressions across all participations with metrics
          example: 2592
        totalReach:
          type: integer
          description: Sum of reach across all participations with metrics
          example: 2023
        totalLikes:
          type: integer
          description: Sum of likes across all participations with metrics
          example: 192
        totalComments:
          type: integer
          description: Sum of comments across all participations with metrics
          example: 112
        totalSaves:
          type: integer
          description: Sum of saves across all participations with metrics
          example: 24
        totalShares:
          type: integer
          description: Sum of shares across all participations with metrics
          example: 5
        participationCountWithMetrics:
          type: integer
          description: Number of participations that have at least one metrics snapshot
          example: 3
        totalParticipationCount:
          type: integer
          description: >-
            Total number of participations in the campaign (regardless of
            metrics presence)
          example: 5
        metricsCoverageRatio:
          type: number
          format: float
          description: Ratio of participations with metrics to total participations (0-1)
          example: 0.6
        earliestSnapshotFetchedAtTimestamp:
          type: string
          format: date-time
          nullable: true
          description: >-
            Earliest fetchedAtTimestamp across all snapshots, or null if no
            snapshots
          example: '2025-11-26T23:39:30.449Z'
        latestSnapshotFetchedAtTimestamp:
          type: string
          format: date-time
          nullable: true
          description: >-
            Latest fetchedAtTimestamp across all snapshots, or null if no
            snapshots
          example: '2025-11-27T10:12:02.100Z'
    ParticipationAnalyticsView:
      type: object
      required:
        - participationId
        - influencerUserId
        - externalMediaId
        - impressions
        - reach
        - likes
        - comments
        - saves
        - shares
        - lastMetricsSyncedAtTimestamp
        - latestSnapshotFetchedAtTimestamp
      properties:
        participationId:
          type: string
          description: Unique identifier for the participation
          example: participation_xyz789
        influencerUserId:
          type: string
          description: Unique identifier for the influencer user
          example: user_123
        externalMediaId:
          type: string
          description: External media ID from the latest snapshot
          example: abc123
        impressions:
          type: integer
          description: Impressions from the latest snapshot
          example: 2592
        reach:
          type: integer
          description: Reach from the latest snapshot
          example: 2023
        likes:
          type: integer
          description: Likes from the latest snapshot
          example: 192
        comments:
          type: integer
          description: Comments from the latest snapshot
          example: 112
        saves:
          type: integer
          description: Saves from the latest snapshot
          example: 24
        shares:
          type: integer
          description: Shares from the latest snapshot
          example: 5
        lastMetricsSyncedAtTimestamp:
          type: string
          format: date-time
          nullable: true
          description: >-
            Last time metrics were synced for this participation (from
            participation entity)
          example: '2025-11-26T23:39:30.449Z'
        latestSnapshotFetchedAtTimestamp:
          type: string
          format: date-time
          description: FetchedAtTimestamp of the latest snapshot
          example: '2025-11-26T23:39:30.449Z'
    CampaignPayoutPreview:
      type: object
      required:
        - campaignId
        - campaignCurrencyCode
        - escrowAvailableAmountCents
        - totalRequestedPayoutAmountCents
        - shortfallAmountCents
        - participationPayoutList
      properties:
        campaignId:
          type: string
          description: Unique identifier for the campaign
          example: campaign_abc123
        campaignCurrencyCode:
          type: string
          enum:
            - NGN
            - USD
          description: Currency code for the campaign
          example: NGN
        escrowAvailableAmountCents:
          type: integer
          description: Current available escrow balance in cents
          example: 5000000
        totalRequestedPayoutAmountCents:
          type: integer
          description: Total requested payout amount across all participations in cents
          example: 3200000
        shortfallAmountCents:
          type: integer
          description: >-
            Shortfall amount if escrow is insufficient (0 if escrow covers all
            payouts)
          example: 0
        participationPayoutList:
          type: array
          description: List of participation payout previews
          items:
            $ref: '#/components/schemas/ParticipationPayoutPreview'
    ParticipationPayoutPreview:
      type: object
      required:
        - participationId
        - influencerUserId
        - externalMediaId
        - impressions
        - reach
        - likes
        - comments
        - saves
        - shares
        - lastMetricsSyncedAtTimestamp
        - latestSnapshotFetchedAtTimestamp
        - grossPayoutAmountCents
      properties:
        participationId:
          type: string
          description: Unique identifier for the participation
          example: participation_xyz789
        influencerUserId:
          type: string
          description: Unique identifier for the influencer user
          example: user_123
        externalMediaId:
          type: string
          description: External media ID from the latest snapshot
          example: abc123
        impressions:
          type: integer
          description: Impressions from the latest snapshot
          example: 2592
        reach:
          type: integer
          description: Reach from the latest snapshot
          example: 2023
        likes:
          type: integer
          description: Likes from the latest snapshot
          example: 192
        comments:
          type: integer
          description: Comments from the latest snapshot
          example: 112
        saves:
          type: integer
          description: Saves from the latest snapshot
          example: 24
        shares:
          type: integer
          description: Shares from the latest snapshot
          example: 5
        lastMetricsSyncedAtTimestamp:
          type: string
          format: date-time
          nullable: true
          description: Last time metrics were synced for this participation
          example: '2025-11-27T08:30:49.957Z'
        latestSnapshotFetchedAtTimestamp:
          type: string
          format: date-time
          description: FetchedAtTimestamp of the latest snapshot
          example: '2025-11-27T08:30:49.957Z'
        grossPayoutAmountCents:
          type: integer
          description: Calculated gross payout amount for this participation in cents
          example: 80000
