openapi: 3.1.0
info:
  title: OfferDaemon API
  version: 1.0.0
  summary: Role-aware REST API for OfferDaemon Dashboard.
  description: |
    OfferDaemon API contract for OfferDaemon Dashboard under `/api/v1`.

    This file is documentation only. It is not imported by the Go server, does
    not generate handlers, does not validate runtime requests, and does not
    change production behavior unless a future build, CI, or runtime integration
    is explicitly added.

    Authentication uses `Authorization: Bearer <api_key>`. The API key resolves
    the tenant company, user, role, and scopes. Callers cannot provide or
    override company scope in request payloads.

    Reads are available on every plan, subject to the API key role and scopes.
    Publisher and merchant API keys are read-only on every plan. Write
    operations are available only to admin keys for Enterprise companies
    outside an active trial, and still require the operation's write scope.
    POST, PATCH, PUT, and DELETE are classified as writes. An ineligible admin
    write returns HTTP 402 with error code `enterprise_plan_required`.

    JSON responses use a `data` envelope for successful responses and an
    `error` envelope for failures. Paginated list responses also include `meta`.
  contact:
    name: OfferDaemon
servers:
  - url: https://{tenant}.offerdaemon.net
    description: Tenant production dashboard host.
    variables:
      tenant:
        default: demo
        description: Company subdomain.
  - url: http://localhost:8080
    description: Local dashboard server.
security:
  - bearerAuth: []
x-offerdaemon:
  source: internal/handler/product_api*.go
  runtime_effect: documentation_only
  route_namespace: /api/v1
  write_access:
    role: admin
    plan: enterprise
    active_trial_allowed: false
    methods: [POST, PATCH, PUT, DELETE]
    denial_status: 402
    denial_code: enterprise_plan_required
tags:
  - name: System
  - name: Profile
  - name: API Keys
  - name: Dashboard
  - name: Offers
  - name: Categories
  - name: News
  - name: Partners
  - name: Reports
  - name: Payments
  - name: Tickets
  - name: Admin Advertisers
  - name: Admin Settings
  - name: Admin Routing
  - name: Admin Logs

paths:
  /api/v1:
    get:
      tags: [System]
      operationId: getApiRootHealth
      summary: Authenticated API health check.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: []
      responses:
        '200':
          description: API key is valid.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/health:
    get:
      tags: [System]
      operationId: getHealth
      summary: Authenticated API health check.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: []
      responses:
        '200':
          description: API key is valid.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/me:
    get:
      tags: [Profile]
      operationId: getMe
      summary: Get the current API user's profile.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["profile:read"]
      responses:
        '200':
          description: Current user profile.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    patch:
      tags: [Profile]
      operationId: patchMe
      summary: Update the current API user's editable profile fields.
      x-roles: [admin]
      x-required-scopes: ["profile:write"]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProfilePatchRequest'
      responses:
        '200':
          description: Updated profile.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/company:
    get:
      tags: [Profile]
      operationId: getCompany
      summary: Get company metadata visible to the caller.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["company:read"]
      responses:
        '200':
          description: Company metadata. Admin keys receive admin-only settings.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompanyResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/me/api-keys:
    get:
      tags: [API Keys]
      operationId: listOwnApiKeys
      summary: List API keys owned by the current user.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["api_keys:read"]
      responses:
        '200':
          description: API keys owned by the current user. Raw tokens are never returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIKeyListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    post:
      tags: [API Keys]
      operationId: createOwnApiKey
      summary: Create an API key owned by the current user.
      description: The raw `token` is returned only once in this response.
      x-roles: [admin]
      x-required-scopes: ["api_keys:write"]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/APIKeyCreateRequest'
      responses:
        '201':
          description: API key created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIKeyCreatedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/me/api-keys/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    delete:
      tags: [API Keys]
      operationId: revokeOwnApiKeyDelete
      summary: Revoke one API key owned by the current user.
      x-roles: [admin]
      x-required-scopes: ["api_keys:write"]
      responses:
        '204':
          description: API key revoked.
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/me/api-keys/{id}/revoke:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [API Keys]
      operationId: revokeOwnApiKeyPost
      summary: Revoke one API key owned by the current user.
      description: Compatibility action endpoint for clients that cannot send DELETE.
      x-roles: [admin]
      x-required-scopes: ["api_keys:write"]
      responses:
        '200':
          description: API key revoked.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RevokedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/api-keys:
    get:
      tags: [API Keys]
      operationId: adminListApiKeys
      summary: List all API keys in the current company.
      x-roles: [admin]
      x-required-scopes: ["api_keys:read"]
      responses:
        '200':
          description: Company API keys. Raw tokens are never returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIKeyListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/api-keys/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    delete:
      tags: [API Keys]
      operationId: adminRevokeApiKeyDelete
      summary: Revoke any non-current company API key.
      x-roles: [admin]
      x-required-scopes: ["api_keys:write"]
      responses:
        '204':
          description: API key revoked.
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/api-keys/{id}/revoke:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [API Keys]
      operationId: adminRevokeApiKeyPost
      summary: Revoke any non-current company API key.
      description: Compatibility action endpoint for clients that cannot send DELETE.
      x-roles: [admin]
      x-required-scopes: ["api_keys:write"]
      responses:
        '200':
          description: API key revoked.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RevokedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/dashboard/summary:
    get:
      tags: [Dashboard]
      operationId: getDashboardSummary
      summary: Get role-aware dashboard summary metrics.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["reports:read"]
      responses:
        '200':
          description: Role-aware dashboard summary.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DashboardSummaryResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/dashboard/hourly-stats:
    get:
      tags: [Dashboard]
      operationId: getDashboardHourlyStats
      summary: Get role-aware hourly dashboard chart buckets.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["reports:read"]
      responses:
        '200':
          description: Hourly dashboard buckets.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HourlyStatsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/offers:
    get:
      tags: [Offers]
      operationId: listOffers
      summary: List offers visible to the current role.
      description: Admin keys receive all company offers. Publisher and merchant keys receive active visible offers.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["offers:read"]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/CategoryID'
      responses:
        '200':
          description: Visible offers.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    post:
      tags: [Offers]
      operationId: createOffer
      summary: Create an offer.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/OfferCreate'
      responses:
        '201':
          description: Offer created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/offers/bulk:
    post:
      tags: [Offers]
      operationId: bulkToggleOffers
      summary: Bulk activate or deactivate offers.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/BulkOfferToggle'
      responses:
        '200':
          description: Number of offers updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdatedCountResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/offers/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Offers]
      operationId: getOffer
      summary: Get one offer visible to the current role.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["offers:read"]
      responses:
        '200':
          description: Offer details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    patch:
      tags: [Offers]
      operationId: updateOffer
      summary: Update an offer.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/OfferPatch'
      responses:
        '200':
          description: Offer updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [Offers]
      operationId: deleteOffer
      summary: Delete an inactive offer.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      responses:
        '200':
          description: Offer deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/offers/{id}/toggle:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Offers]
      operationId: toggleOffer
      summary: Toggle offer active state.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      responses:
        '200':
          description: Offer after toggle.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/offers/{id}/landing-pages:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Offers]
      operationId: listOfferLandingPages
      summary: List landing pages for an offer.
      description: Admin keys receive all landing page fields. Publisher and merchant keys receive active landing pages only.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["offers:read"]
      responses:
        '200':
          description: Landing pages.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LandingPageListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    post:
      tags: [Offers]
      operationId: createOfferLandingPage
      summary: Create an offer landing page.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/LandingPageCreate'
      responses:
        '201':
          description: Landing page created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LandingPageResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/offers/{id}/landing-pages/{slug}:
    parameters:
      - $ref: '#/components/parameters/ID'
      - $ref: '#/components/parameters/Slug'
    patch:
      tags: [Offers]
      operationId: updateOfferLandingPage
      summary: Update an offer landing page by slug.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/LandingPagePatch'
      responses:
        '200':
          description: Landing page updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LandingPageResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [Offers]
      operationId: deleteOfferLandingPage
      summary: Delete an offer landing page by slug.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      responses:
        '200':
          description: Landing page deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/offers/{id}/tracking-link:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Offers]
      operationId: getTrackingLink
      summary: Generate a tracking link for an offer.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["tracking_links:read"]
      parameters:
        - $ref: '#/components/parameters/PublisherIDQuery'
        - $ref: '#/components/parameters/LandingPageQuery'
        - $ref: '#/components/parameters/Sub1'
        - $ref: '#/components/parameters/Sub2'
        - $ref: '#/components/parameters/Sub3'
        - $ref: '#/components/parameters/Sub4'
        - $ref: '#/components/parameters/Sub5'
        - $ref: '#/components/parameters/Sub6'
        - $ref: '#/components/parameters/Sub7'
        - $ref: '#/components/parameters/Sub8'
        - $ref: '#/components/parameters/Sub9'
        - $ref: '#/components/parameters/Sub10'
      responses:
        '200':
          description: Generated tracking link.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrackingLinkResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    post:
      tags: [Offers]
      operationId: createTrackingLink
      summary: Generate a tracking link for an offer using a JSON body.
      x-roles: [admin]
      x-required-scopes: ["tracking_links:read"]
      requestBody:
        $ref: '#/components/requestBodies/TrackingLink'
      responses:
        '200':
          description: Generated tracking link.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrackingLinkResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/categories:
    get:
      tags: [Categories]
      operationId: listCategories
      summary: List offer categories.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["offers:read"]
      responses:
        '200':
          description: Offer categories.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CategoryListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    post:
      tags: [Categories]
      operationId: createCategory
      summary: Create an offer category.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/CategoryCreate'
      responses:
        '201':
          description: Category created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CategoryResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/categories/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    patch:
      tags: [Categories]
      operationId: updateCategory
      summary: Update an offer category.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/CategoryPatch'
      responses:
        '200':
          description: Category updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CategoryResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [Categories]
      operationId: deleteCategory
      summary: Delete an offer category.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      responses:
        '200':
          description: Category deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/news:
    get:
      tags: [News]
      operationId: listVisibleNews
      summary: List recent company news visible to the caller.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["company:read"]
      responses:
        '200':
          description: Recent visible news.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/partners:
    get:
      tags: [Partners]
      operationId: listPartners
      summary: List company partners.
      x-roles: [admin]
      x-required-scopes: ["partners:read"]
      parameters:
        - $ref: '#/components/parameters/Search'
        - $ref: '#/components/parameters/RoleQuery'
        - $ref: '#/components/parameters/StatusQuery'
      responses:
        '200':
          description: Partners.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PartnerListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/partners/bulk:
    post:
      tags: [Partners]
      operationId: bulkPartnerStatus
      summary: Bulk update partner status.
      description: "partners:write covers publisher and merchant targets. Any admin target additionally requires admin_full."
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      requestBody:
        $ref: '#/components/requestBodies/PartnerBulkStatus'
      responses:
        '200':
          description: Number of partners updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdatedCountResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/partners/invite:
    post:
      tags: [Partners]
      operationId: invitePartner
      summary: Invite a publisher.
      description: Currently registered but returns 501 until Supabase Admin and mailer wiring is completed.
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      responses:
        '501':
          $ref: '#/components/responses/NotImplemented'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/partners/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Partners]
      operationId: getPartner
      summary: Get a partner profile.
      x-roles: [admin]
      x-required-scopes: ["partners:read"]
      responses:
        '200':
          description: Partner profile.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    patch:
      tags: [Partners]
      operationId: updatePartner
      summary: Update partner metadata, role, status, relationships, or notes.
      description: "Granting the admin role or changing the role/status of an existing admin additionally requires admin_full."
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      requestBody:
        $ref: '#/components/requestBodies/PartnerPatch'
      responses:
        '200':
          description: Partner updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [Partners]
      operationId: deletePartner
      summary: Soft-delete and anonymize a partner.
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      responses:
        '200':
          description: Partner deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/partners/{id}/approve:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Partners]
      operationId: approvePartner
      summary: Approve a partner.
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      responses:
        '200':
          description: Partner approved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/partners/{id}/reject:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Partners]
      operationId: rejectPartner
      summary: Reject a partner.
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      responses:
        '200':
          description: Partner rejected.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/partners/{id}/resend-invite:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Partners]
      operationId: resendPartnerInvite
      summary: Resend a partner invite.
      description: Currently registered but returns 501 until Supabase Admin and mailer wiring is completed.
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      responses:
        '501':
          $ref: '#/components/responses/NotImplemented'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/partners/{id}/send-reset:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Partners]
      operationId: sendPartnerReset
      summary: Send a password reset email to a partner.
      description: Currently registered but returns 501 until Supabase Admin and mailer wiring is completed.
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      responses:
        '501':
          $ref: '#/components/responses/NotImplemented'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/partners/{id}/links:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Partners]
      operationId: listPartnerLinks
      summary: List active offer tracking links for one partner.
      x-roles: [admin]
      x-required-scopes: ["tracking_links:read"]
      responses:
        '200':
          description: Partner tracking links.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PartnerLinkListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/conversions:
    get:
      tags: [Reports]
      operationId: listConversions
      summary: List role-aware conversion report rows.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["conversions:read"]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/OfferIDQuery'
        - $ref: '#/components/parameters/StatusQuery'
        - $ref: '#/components/parameters/EventType'
        - $ref: '#/components/parameters/SettlementStatus'
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - $ref: '#/components/parameters/BeneficiaryType'
        - $ref: '#/components/parameters/Search'
        - $ref: '#/components/parameters/PublisherIDQuery'
        - $ref: '#/components/parameters/ScopeQuery'
      responses:
        '200':
          description: Conversion report rows.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConversionListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/clicks:
    get:
      tags: [Reports]
      operationId: listClicks
      summary: List role-aware click report rows.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["clicks:read"]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/OfferSlug'
        - $ref: '#/components/parameters/Country'
        - $ref: '#/components/parameters/DeviceType'
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - $ref: '#/components/parameters/Search'
        - $ref: '#/components/parameters/PublisherSearch'
        - $ref: '#/components/parameters/FraudBucket'
        - $ref: '#/components/parameters/IsProxy'
        - $ref: '#/components/parameters/IsDatacenter'
      responses:
        '200':
          description: Click report rows.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClickListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/reports/performance:
    get:
      tags: [Reports]
      operationId: getPerformanceReport
      summary: Get role-aware performance report metrics.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["reports:read"]
      parameters:
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - $ref: '#/components/parameters/Granularity'
        - $ref: '#/components/parameters/GroupBy'
        - $ref: '#/components/parameters/OfferSlug'
        - $ref: '#/components/parameters/PublisherIDQuery'
        - $ref: '#/components/parameters/Country'
        - $ref: '#/components/parameters/EventType'
        - $ref: '#/components/parameters/StatusQuery'
        - $ref: '#/components/parameters/BeneficiaryType'
        - $ref: '#/components/parameters/ScopeQuery'
        - $ref: '#/components/parameters/OS'
        - $ref: '#/components/parameters/Browser'
        - $ref: '#/components/parameters/ISP'
        - $ref: '#/components/parameters/DeviceType'
        - $ref: '#/components/parameters/Sub1'
        - $ref: '#/components/parameters/Sub2'
        - $ref: '#/components/parameters/Sub3'
        - $ref: '#/components/parameters/Sub4'
        - $ref: '#/components/parameters/Sub5'
      responses:
        '200':
          description: Performance report.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PerformanceResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/reports/trends:
    get:
      tags: [Reports]
      operationId: getTrendsReport
      summary: Get period-over-period trend metrics.
      description: This route is currently wired to the same handler contract as the performance report.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["reports:read"]
      parameters:
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - $ref: '#/components/parameters/Granularity'
        - $ref: '#/components/parameters/OfferSlug'
        - $ref: '#/components/parameters/Sub1'
      responses:
        '200':
          description: Trend or performance-shaped report data.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PerformanceResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/reports/top-publishers:
    get:
      tags: [Reports]
      operationId: getTopPublishersReport
      summary: Get top publishers report.
      x-roles: [admin, merchant]
      x-required-scopes: ["reports:read"]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - $ref: '#/components/parameters/OfferSlug'
        - $ref: '#/components/parameters/ScopeQuery'
        - $ref: '#/components/parameters/StatusQuery'
        - $ref: '#/components/parameters/SortBy'
      responses:
        '200':
          description: Top publishers rows.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TopPublisherListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/reports/fraud:
    get:
      tags: [Reports]
      operationId: getFraudReport
      summary: Get fraud analysis report.
      x-roles: [admin, merchant]
      x-required-scopes: ["reports:read"]
      parameters:
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Fraud report.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FraudResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/search/offers:
    get:
      tags: [Offers]
      operationId: searchOffers
      summary: Search offers visible to the current role.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["offers:read"]
      parameters:
        - $ref: '#/components/parameters/Q'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/CategoryID'
      responses:
        '200':
          description: Offer search results.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchResultListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/merchant/publishers:
    get:
      tags: [Partners]
      operationId: listMerchantPublishers
      summary: List publishers managed by the current merchant.
      x-roles: [merchant]
      x-required-scopes: ["partners:read"]
      parameters:
        - $ref: '#/components/parameters/Search'
      responses:
        '200':
          description: Managed publishers.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MerchantPublisherListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/merchant/publishers/search:
    get:
      tags: [Partners]
      operationId: searchMerchantPublishers
      summary: Search publishers managed by the current merchant.
      x-roles: [merchant]
      x-required-scopes: ["partners:read"]
      parameters:
        - $ref: '#/components/parameters/Q'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Managed publisher search results.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchResultListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/merchant/search/publishers:
    get:
      tags: [Partners]
      operationId: searchMerchantPublishersAlias
      summary: Search publishers managed by the current merchant.
      description: Alias of `/api/v1/merchant/publishers/search`.
      x-roles: [merchant]
      x-required-scopes: ["partners:read"]
      parameters:
        - $ref: '#/components/parameters/Q'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Managed publisher search results.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchResultListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/payments:
    get:
      tags: [Payments]
      operationId: listPayments
      summary: List role-aware payments.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["payments:read"]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/StatusQuery'
      responses:
        '200':
          description: Payments.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/payments/balance:
    get:
      tags: [Payments]
      operationId: getPaymentBalance
      summary: Get payable balance.
      description: Admin keys may pass `beneficiary_id`; publisher and merchant keys always receive their own balance.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["payments:read"]
      parameters:
        - $ref: '#/components/parameters/BeneficiaryID'
      responses:
        '200':
          description: Balance.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BalanceResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/payments/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Payments]
      operationId: getPayment
      summary: Get payment detail with linked conversions.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["payments:read"]
      responses:
        '200':
          description: Payment detail.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/tickets:
    get:
      tags: [Tickets]
      operationId: listTickets
      summary: List role-aware support tickets.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["tickets:read"]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/StatusQuery'
        - $ref: '#/components/parameters/PublisherIDQuery'
      responses:
        '200':
          description: Tickets.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TicketListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    post:
      tags: [Tickets]
      operationId: createTicket
      summary: Create a support ticket.
      x-roles: [admin]
      x-required-scopes: ["tickets:write"]
      requestBody:
        $ref: '#/components/requestBodies/TicketCreate'
      responses:
        '201':
          description: Ticket created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TicketCreatedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/tickets/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Tickets]
      operationId: getTicket
      summary: Get ticket detail and messages.
      x-roles: [admin, publisher, merchant]
      x-required-scopes: ["tickets:read"]
      responses:
        '200':
          description: Ticket detail.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TicketResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/tickets/{id}/messages:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Tickets]
      operationId: createTicketMessage
      summary: Reply to a ticket.
      x-roles: [admin]
      x-required-scopes: ["tickets:write"]
      requestBody:
        $ref: '#/components/requestBodies/TicketMessage'
      responses:
        '201':
          description: Ticket message created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatedBoolResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/tickets/{id}/status:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Tickets]
      operationId: updateTicketStatus
      summary: Change ticket status.
      description: Publisher and merchant keys are read-only.
      x-roles: [admin]
      x-required-scopes: ["tickets:write"]
      requestBody:
        $ref: '#/components/requestBodies/TicketStatus'
      responses:
        '200':
          description: Ticket status updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/advertisers:
    get:
      tags: [Admin Advertisers]
      operationId: adminListAdvertisers
      summary: List advertisers.
      x-roles: [admin]
      x-required-scopes: ["offers:read"]
      responses:
        '200':
          description: Advertisers.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdvertiserListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    post:
      tags: [Admin Advertisers]
      operationId: adminCreateAdvertiser
      summary: Create an advertiser.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/AdvertiserCreate'
      responses:
        '201':
          description: Advertiser created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdvertiserResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/advertisers/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    patch:
      tags: [Admin Advertisers]
      operationId: adminUpdateAdvertiser
      summary: Update an advertiser.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/AdvertiserPatch'
      responses:
        '200':
          description: Advertiser updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdvertiserResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [Admin Advertisers]
      operationId: adminDeleteAdvertiser
      summary: Delete an advertiser.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      responses:
        '200':
          description: Advertiser deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/categories:
    get:
      tags: [Categories]
      operationId: adminListCategories
      summary: List offer categories.
      x-roles: [admin]
      x-required-scopes: ["offers:read"]
      responses:
        '200':
          description: Offer categories.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CategoryListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    post:
      tags: [Categories]
      operationId: adminCreateCategory
      summary: Create an offer category.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/CategoryCreate'
      responses:
        '201':
          description: Category created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CategoryResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/categories/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    patch:
      tags: [Categories]
      operationId: adminUpdateCategory
      summary: Update an offer category.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/CategoryPatch'
      responses:
        '200':
          description: Category updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CategoryResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [Categories]
      operationId: adminDeleteCategory
      summary: Delete an offer category.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      responses:
        '200':
          description: Category deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/offers:
    get:
      tags: [Offers]
      operationId: adminListOffers
      summary: List all company offers.
      x-roles: [admin]
      x-required-scopes: ["offers:read"]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
      responses:
        '200':
          description: Company offers.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    post:
      tags: [Offers]
      operationId: adminCreateOffer
      summary: Create an offer.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/OfferCreate'
      responses:
        '201':
          description: Offer created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/offers/bulk:
    post:
      tags: [Offers]
      operationId: adminBulkToggleOffers
      summary: Bulk activate or deactivate offers.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/BulkOfferToggle'
      responses:
        '200':
          description: Number of offers updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdatedCountResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/offers/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    patch:
      tags: [Offers]
      operationId: adminUpdateOffer
      summary: Update an offer.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/OfferPatch'
      responses:
        '200':
          description: Offer updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [Offers]
      operationId: adminDeleteOffer
      summary: Delete an inactive offer.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      responses:
        '200':
          description: Offer deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/offers/{id}/toggle:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Offers]
      operationId: adminToggleOffer
      summary: Toggle offer active state.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      responses:
        '200':
          description: Offer after toggle.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/offers/{id}/landing-pages:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Offers]
      operationId: adminListOfferLandingPages
      summary: List all landing pages for an offer.
      x-roles: [admin]
      x-required-scopes: ["offers:read"]
      responses:
        '200':
          description: Landing pages.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LandingPageListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    post:
      tags: [Offers]
      operationId: adminCreateOfferLandingPage
      summary: Create an offer landing page.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/LandingPageCreate'
      responses:
        '201':
          description: Landing page created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LandingPageResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    put:
      tags: [Offers]
      operationId: adminSaveOfferLandingPages
      summary: Replace all landing pages for an offer.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/LandingPageSave'
      responses:
        '200':
          description: Saved landing pages.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LandingPageListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/offers/{id}/landing-pages/{slug}:
    parameters:
      - $ref: '#/components/parameters/ID'
      - $ref: '#/components/parameters/Slug'
    patch:
      tags: [Offers]
      operationId: adminUpdateOfferLandingPage
      summary: Update an offer landing page by slug.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/LandingPagePatch'
      responses:
        '200':
          description: Landing page updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LandingPageResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [Offers]
      operationId: adminDeleteOfferLandingPage
      summary: Delete an offer landing page by slug.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      responses:
        '200':
          description: Landing page deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/offers/{id}/caps:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Offers]
      operationId: adminListOfferCaps
      summary: List offer caps.
      x-roles: [admin]
      x-required-scopes: ["offers:read"]
      responses:
        '200':
          description: Offer caps.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferCapListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    put:
      tags: [Offers]
      operationId: adminSaveOfferCaps
      summary: Replace offer caps.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/OfferCapsSave'
      responses:
        '200':
          description: Saved offer caps.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferCapListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/offers/{id}/rate-rules:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Offers]
      operationId: adminListOfferRateRules
      summary: List offer rate rules.
      x-roles: [admin]
      x-required-scopes: ["offers:read"]
      responses:
        '200':
          description: Offer rate rules.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateRuleListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    put:
      tags: [Offers]
      operationId: adminSaveOfferRateRules
      summary: Replace offer rate rules.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/RateRulesSave'
      responses:
        '200':
          description: Saved rate rules.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateRuleListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/offers/{id}/publisher-overrides:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Offers]
      operationId: adminListPublisherOverrides
      summary: List per-publisher offer overrides.
      x-roles: [admin]
      x-required-scopes: ["offers:read"]
      responses:
        '200':
          description: Publisher overrides.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublisherOverrideListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/offers/{id}/publisher-overrides/{publisher_id}:
    parameters:
      - $ref: '#/components/parameters/ID'
      - $ref: '#/components/parameters/PublisherIDPath'
    put:
      tags: [Offers]
      operationId: adminUpsertPublisherOverride
      summary: Create or update a per-publisher offer override.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/PublisherOverrideUpsert'
      responses:
        '200':
          description: Publisher override saved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublisherOverrideUpsertResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/offers/{id}/publisher-overrides/{override_id}:
    parameters:
      - $ref: '#/components/parameters/ID'
      - $ref: '#/components/parameters/OverrideID'
    delete:
      tags: [Offers]
      operationId: adminDeletePublisherOverrideByID
      summary: Delete a per-publisher offer override by override id.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      responses:
        '200':
          description: Publisher override deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/offers/{id}/publisher-rates:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Offers]
      operationId: adminListPublisherRates
      summary: List per-publisher offer rates.
      description: Alias of publisher overrides.
      x-roles: [admin]
      x-required-scopes: ["offers:read"]
      responses:
        '200':
          description: Publisher overrides.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublisherOverrideListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/offers/{id}/publisher-rates/{publisher_id}:
    parameters:
      - $ref: '#/components/parameters/ID'
      - $ref: '#/components/parameters/PublisherIDPath'
    put:
      tags: [Offers]
      operationId: adminUpsertPublisherRate
      summary: Create or update a per-publisher offer rate.
      description: Alias of publisher overrides.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      requestBody:
        $ref: '#/components/requestBodies/PublisherOverrideUpsert'
      responses:
        '200':
          description: Publisher override saved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublisherOverrideUpsertResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [Offers]
      operationId: adminDeletePublisherRate
      summary: Delete a per-publisher offer rate by publisher id.
      description: Alias of publisher overrides.
      x-roles: [admin]
      x-required-scopes: ["offers:write"]
      responses:
        '200':
          description: Publisher override deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/partners:
    get:
      tags: [Partners]
      operationId: adminListPartners
      summary: List company partners.
      x-roles: [admin]
      x-required-scopes: ["partners:read"]
      parameters:
        - $ref: '#/components/parameters/Search'
        - $ref: '#/components/parameters/RoleQuery'
        - $ref: '#/components/parameters/StatusQuery'
      responses:
        '200':
          description: Partners.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PartnerListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/partners/bulk:
    post:
      tags: [Partners]
      operationId: adminBulkPartnerStatus
      summary: Bulk update partner status.
      description: "partners:write covers publisher and merchant targets. Any admin target additionally requires admin_full."
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      requestBody:
        $ref: '#/components/requestBodies/PartnerBulkStatus'
      responses:
        '200':
          description: Number of partners updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdatedCountResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/partners/invite:
    post:
      tags: [Partners]
      operationId: adminInvitePartner
      summary: Invite a publisher.
      description: Currently registered but returns 501 until Supabase Admin and mailer wiring is completed.
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      responses:
        '501':
          $ref: '#/components/responses/NotImplemented'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/partners/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Partners]
      operationId: adminGetPartner
      summary: Get a partner profile.
      x-roles: [admin]
      x-required-scopes: ["partners:read"]
      responses:
        '200':
          description: Partner profile.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    patch:
      tags: [Partners]
      operationId: adminUpdatePartner
      summary: Update partner metadata, role, status, relationships, or notes.
      description: "Granting the admin role or changing the role/status of an existing admin additionally requires admin_full."
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      requestBody:
        $ref: '#/components/requestBodies/PartnerPatch'
      responses:
        '200':
          description: Partner updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [Partners]
      operationId: adminDeletePartner
      summary: Soft-delete and anonymize a partner.
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      responses:
        '200':
          description: Partner deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/partners/{id}/approve:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Partners]
      operationId: adminApprovePartner
      summary: Approve a partner.
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      responses:
        '200':
          description: Partner approved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/partners/{id}/reject:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Partners]
      operationId: adminRejectPartner
      summary: Reject a partner.
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      responses:
        '200':
          description: Partner rejected.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/partners/{id}/resend-invite:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Partners]
      operationId: adminResendPartnerInvite
      summary: Resend a partner invite.
      description: Currently registered but returns 501 until Supabase Admin and mailer wiring is completed.
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      responses:
        '501':
          $ref: '#/components/responses/NotImplemented'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/partners/{id}/send-reset:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Partners]
      operationId: adminSendPartnerReset
      summary: Send a password reset email to a partner.
      description: Currently registered but returns 501 until Supabase Admin and mailer wiring is completed.
      x-roles: [admin]
      x-required-scopes: ["partners:write"]
      responses:
        '501':
          $ref: '#/components/responses/NotImplemented'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/partners/{id}/links:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Partners]
      operationId: adminListPartnerLinks
      summary: List active offer tracking links for one partner.
      x-roles: [admin]
      x-required-scopes: ["tracking_links:read"]
      responses:
        '200':
          description: Partner tracking links.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PartnerLinkListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/conversions:
    get:
      tags: [Reports]
      operationId: adminListConversions
      summary: List admin conversion report rows.
      x-roles: [admin]
      x-required-scopes: ["conversions:read"]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/OfferIDQuery'
        - $ref: '#/components/parameters/StatusQuery'
        - $ref: '#/components/parameters/EventType'
        - $ref: '#/components/parameters/SettlementStatus'
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - $ref: '#/components/parameters/BeneficiaryType'
        - $ref: '#/components/parameters/Search'
      responses:
        '200':
          description: Admin conversion report rows.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConversionListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/conversions/manual:
    post:
      tags: [Reports]
      operationId: adminCreateManualConversion
      summary: Create a manual conversion.
      x-roles: [admin]
      x-required-scopes: ["conversions:write"]
      requestBody:
        $ref: '#/components/requestBodies/ManualConversion'
      responses:
        '201':
          description: Manual conversion result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ManualConversionResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/conversions/{id}/approve:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Reports]
      operationId: adminApproveConversion
      summary: Approve a conversion.
      x-roles: [admin]
      x-required-scopes: ["conversions:write"]
      responses:
        '200':
          description: Conversion approved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/conversions/{id}/reject:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Reports]
      operationId: adminRejectConversion
      summary: Reject a conversion.
      x-roles: [admin]
      x-required-scopes: ["conversions:write"]
      responses:
        '200':
          description: Conversion rejected.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/conversions/bulk:
    post:
      tags: [Reports]
      operationId: adminBulkConversionStatus
      summary: Bulk approve or reject conversions.
      x-roles: [admin]
      x-required-scopes: ["conversions:write"]
      requestBody:
        $ref: '#/components/requestBodies/ConversionBulkStatus'
      responses:
        '200':
          description: Bulk conversion update result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkConversionStatusResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/conversions/bulk/approve:
    post:
      tags: [Reports]
      operationId: adminBulkApproveConversions
      summary: Bulk approve conversions.
      x-roles: [admin]
      x-required-scopes: ["conversions:write"]
      requestBody:
        $ref: '#/components/requestBodies/ConversionBulkIDs'
      responses:
        '200':
          description: Bulk conversion update result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkConversionStatusResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/conversions/bulk/reject:
    post:
      tags: [Reports]
      operationId: adminBulkRejectConversions
      summary: Bulk reject conversions.
      x-roles: [admin]
      x-required-scopes: ["conversions:write"]
      requestBody:
        $ref: '#/components/requestBodies/ConversionBulkIDs'
      responses:
        '200':
          description: Bulk conversion update result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkConversionStatusResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/payments:
    get:
      tags: [Payments]
      operationId: adminListPayments
      summary: List company payments.
      x-roles: [admin]
      x-required-scopes: ["payments:read"]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/StatusQuery'
      responses:
        '200':
          description: Payments.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/payments/generate:
    post:
      tags: [Payments]
      operationId: adminGeneratePayment
      summary: Generate a payment for a beneficiary.
      x-roles: [admin]
      x-required-scopes: ["payments:write"]
      requestBody:
        $ref: '#/components/requestBodies/AdminPaymentGenerate'
      responses:
        '201':
          description: Payment generated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/payments/{id}/approve:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Payments]
      operationId: adminApprovePayment
      summary: Approve a payment.
      x-roles: [admin]
      x-required-scopes: ["payments:write"]
      requestBody:
        $ref: '#/components/requestBodies/AdminPaymentApprove'
      responses:
        '200':
          description: Payment approved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/payments/{id}/reject:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Payments]
      operationId: adminRejectPayment
      summary: Reject a payment.
      x-roles: [admin]
      x-required-scopes: ["payments:write"]
      requestBody:
        $ref: '#/components/requestBodies/AdminPaymentNotes'
      responses:
        '200':
          description: Payment rejected.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/payments/{id}/processing:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Payments]
      operationId: adminMarkPaymentProcessing
      summary: Mark a payment as processing.
      x-roles: [admin]
      x-required-scopes: ["payments:write"]
      requestBody:
        $ref: '#/components/requestBodies/AdminPaymentNotes'
      responses:
        '200':
          description: Payment marked processing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/payments/{id}/paid:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Payments]
      operationId: adminMarkPaymentPaid
      summary: Mark a payment as paid.
      x-roles: [admin]
      x-required-scopes: ["payments:write"]
      requestBody:
        $ref: '#/components/requestBodies/AdminPaymentPaid'
      responses:
        '200':
          description: Payment marked paid.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/payments/{id}/cancel:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Payments]
      operationId: adminCancelPayment
      summary: Cancel a payment.
      x-roles: [admin]
      x-required-scopes: ["payments:write"]
      requestBody:
        $ref: '#/components/requestBodies/AdminPaymentNotes'
      responses:
        '200':
          description: Payment cancelled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/payment-methods:
    get:
      tags: [Payments]
      operationId: adminListPaymentMethods
      summary: List company payment methods.
      x-roles: [admin]
      x-required-scopes: ["settings:read"]
      responses:
        '200':
          description: Payment methods.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentMethodListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    post:
      tags: [Payments]
      operationId: adminCreatePaymentMethod
      summary: Create a payment method.
      x-roles: [admin]
      x-required-scopes: ["settings:write"]
      requestBody:
        $ref: '#/components/requestBodies/PaymentMethodCreate'
      responses:
        '201':
          description: Payment method created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentMethodResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/payment-methods/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    patch:
      tags: [Payments]
      operationId: adminUpdatePaymentMethod
      summary: Update a payment method.
      x-roles: [admin]
      x-required-scopes: ["settings:write"]
      requestBody:
        $ref: '#/components/requestBodies/PaymentMethodPatch'
      responses:
        '200':
          description: Payment method updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentMethodResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [Payments]
      operationId: adminDeletePaymentMethod
      summary: Delete a payment method.
      x-roles: [admin]
      x-required-scopes: ["settings:write"]
      responses:
        '200':
          description: Payment method deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/redirect-domains:
    get:
      tags: [Admin Settings]
      operationId: adminListRedirectDomains
      summary: List redirect domains.
      x-roles: [admin]
      x-required-scopes: ["settings:read"]
      responses:
        '200':
          description: Redirect domains.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RedirectDomainListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    post:
      tags: [Admin Settings]
      operationId: adminCreateRedirectDomain
      summary: Add a redirect domain.
      x-roles: [admin]
      x-required-scopes: ["settings:write"]
      requestBody:
        $ref: '#/components/requestBodies/RedirectDomainCreate'
      responses:
        '201':
          description: Redirect domain created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RedirectDomainResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/redirect-domains/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    delete:
      tags: [Admin Settings]
      operationId: adminDeleteRedirectDomain
      summary: Delete a redirect domain.
      x-roles: [admin]
      x-required-scopes: ["settings:write"]
      responses:
        '200':
          description: Redirect domain deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/redirect-domains/{id}/default:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Admin Settings]
      operationId: adminSetDefaultRedirectDomain
      summary: Set a redirect domain as default.
      x-roles: [admin]
      x-required-scopes: ["settings:write"]
      responses:
        '200':
          description: Default redirect domain updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DefaultResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/redirect-domains/{id}/verify:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Admin Settings]
      operationId: adminVerifyRedirectDomain
      summary: Verify redirect domain CNAME.
      x-roles: [admin]
      x-required-scopes: ["settings:write"]
      responses:
        '200':
          description: Verification result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerifiedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/settings:
    get:
      tags: [Admin Settings]
      operationId: adminGetSettings
      summary: Get admin company settings.
      x-roles: [admin]
      x-required-scopes: ["settings:read"]
      responses:
        '200':
          description: Company settings.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompanyResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    patch:
      tags: [Admin Settings]
      operationId: adminPatchSettings
      summary: Update JSON-safe company settings.
      x-roles: [admin]
      x-required-scopes: ["settings:write"]
      requestBody:
        $ref: '#/components/requestBodies/SettingsPatch'
      responses:
        '200':
          description: Updated company settings.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompanyResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/settings/custom-fields:
    get:
      tags: [Admin Settings]
      operationId: adminGetCustomFields
      summary: Get offer custom field settings.
      x-roles: [admin]
      x-required-scopes: ["settings:read"]
      responses:
        '200':
          description: Custom field settings.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomFieldsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    put:
      tags: [Admin Settings]
      operationId: adminPutCustomFields
      summary: Replace offer custom field settings.
      x-roles: [admin]
      x-required-scopes: ["settings:write"]
      requestBody:
        $ref: '#/components/requestBodies/CustomFieldsPut'
      responses:
        '200':
          description: Updated custom field settings.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomFieldsResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/audience/test:
    post:
      tags: [Admin Settings]
      operationId: adminTestAudienceProvider
      summary: Test the configured audience provider connection.
      x-roles: [admin]
      x-required-scopes: ["settings:write"]
      responses:
        '200':
          description: Audience provider test result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AudienceTestResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/audience/sync:
    post:
      tags: [Admin Settings]
      operationId: adminStartAudienceSync
      summary: Start a full audience sync.
      x-roles: [admin]
      x-required-scopes: ["settings:write"]
      responses:
        '202':
          description: Audience sync queued.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AudienceSyncQueuedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/audience/sync/status:
    get:
      tags: [Admin Settings]
      operationId: adminGetAudienceSyncStatus
      summary: Get audience sync status.
      x-roles: [admin]
      x-required-scopes: ["settings:read"]
      responses:
        '200':
          description: Audience sync status.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AudienceSyncStatusResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/routing/profiles:
    get:
      tags: [Admin Routing]
      operationId: adminListRoutingProfilesSlash
      summary: List routing profiles.
      x-roles: [admin]
      x-required-scopes: ["routing:read"]
      responses:
        '200':
          description: Routing profiles.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoutingProfileListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    post:
      tags: [Admin Routing]
      operationId: adminCreateRoutingProfileSlash
      summary: Create a routing profile.
      x-roles: [admin]
      x-required-scopes: ["routing:write"]
      requestBody:
        $ref: '#/components/requestBodies/RoutingProfileCreate'
      responses:
        '201':
          description: Routing profile created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IDResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/routing/profiles/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Admin Routing]
      operationId: adminGetRoutingProfileSlash
      summary: Get routing profile detail.
      x-roles: [admin]
      x-required-scopes: ["routing:read"]
      responses:
        '200':
          description: Routing profile detail.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoutingProfileResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    patch:
      tags: [Admin Routing]
      operationId: adminUpdateRoutingProfileSlash
      summary: Update a routing profile.
      x-roles: [admin]
      x-required-scopes: ["routing:write"]
      requestBody:
        $ref: '#/components/requestBodies/RoutingProfilePatch'
      responses:
        '200':
          description: Routing profile updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdatedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [Admin Routing]
      operationId: adminDeleteRoutingProfileSlash
      summary: Delete a routing profile.
      x-roles: [admin]
      x-required-scopes: ["routing:write"]
      responses:
        '200':
          description: Routing profile deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/routing/profiles/{id}/rules:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Admin Routing]
      operationId: adminCreateRoutingRuleSlash
      summary: Create a routing rule under a profile.
      x-roles: [admin]
      x-required-scopes: ["routing:write"]
      requestBody:
        $ref: '#/components/requestBodies/RoutingRuleCreate'
      responses:
        '201':
          description: Routing rule created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IDResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/routing/rules/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    patch:
      tags: [Admin Routing]
      operationId: adminUpdateRoutingRule
      summary: Update a routing rule.
      x-roles: [admin]
      x-required-scopes: ["routing:write"]
      requestBody:
        $ref: '#/components/requestBodies/RoutingRulePatch'
      responses:
        '200':
          description: Routing rule updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdatedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [Admin Routing]
      operationId: adminDeleteRoutingRule
      summary: Delete a routing rule.
      x-roles: [admin]
      x-required-scopes: ["routing:write"]
      responses:
        '200':
          description: Routing rule deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/routing/rules/{id}/offers:
    parameters:
      - $ref: '#/components/parameters/ID'
    put:
      tags: [Admin Routing]
      operationId: adminSyncRoutingRuleOffers
      summary: Replace weighted offers for a routing rule.
      x-roles: [admin]
      x-required-scopes: ["routing:write"]
      requestBody:
        $ref: '#/components/requestBodies/RoutingRuleOffers'
      responses:
        '200':
          description: Routing rule offers updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdatedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/routing-profiles:
    get:
      tags: [Admin Routing]
      operationId: adminListRoutingProfiles
      summary: List routing profiles.
      description: Alias of `/api/v1/admin/routing/profiles`.
      x-roles: [admin]
      x-required-scopes: ["routing:read"]
      responses:
        '200':
          description: Routing profiles.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoutingProfileListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    post:
      tags: [Admin Routing]
      operationId: adminCreateRoutingProfile
      summary: Create a routing profile.
      description: Alias of `/api/v1/admin/routing/profiles`.
      x-roles: [admin]
      x-required-scopes: ["routing:write"]
      requestBody:
        $ref: '#/components/requestBodies/RoutingProfileCreate'
      responses:
        '201':
          description: Routing profile created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IDResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/routing-profiles/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    get:
      tags: [Admin Routing]
      operationId: adminGetRoutingProfile
      summary: Get routing profile detail.
      description: Alias of `/api/v1/admin/routing/profiles/{id}`.
      x-roles: [admin]
      x-required-scopes: ["routing:read"]
      responses:
        '200':
          description: Routing profile detail.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoutingProfileResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    patch:
      tags: [Admin Routing]
      operationId: adminUpdateRoutingProfile
      summary: Update a routing profile.
      description: Alias of `/api/v1/admin/routing/profiles/{id}`.
      x-roles: [admin]
      x-required-scopes: ["routing:write"]
      requestBody:
        $ref: '#/components/requestBodies/RoutingProfilePatch'
      responses:
        '200':
          description: Routing profile updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdatedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [Admin Routing]
      operationId: adminDeleteRoutingProfile
      summary: Delete a routing profile.
      description: Alias of `/api/v1/admin/routing/profiles/{id}`.
      x-roles: [admin]
      x-required-scopes: ["routing:write"]
      responses:
        '200':
          description: Routing profile deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/routing-profiles/{id}/rules:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [Admin Routing]
      operationId: adminCreateRoutingRule
      summary: Create a routing rule under a profile.
      description: Alias of `/api/v1/admin/routing/profiles/{id}/rules`.
      x-roles: [admin]
      x-required-scopes: ["routing:write"]
      requestBody:
        $ref: '#/components/requestBodies/RoutingRuleCreate'
      responses:
        '201':
          description: Routing rule created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IDResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/routing-profiles/{id}/rules/{rule_id}:
    parameters:
      - $ref: '#/components/parameters/ID'
      - $ref: '#/components/parameters/RuleID'
    patch:
      tags: [Admin Routing]
      operationId: adminUpdateRoutingRuleAlias
      summary: Update a routing rule.
      description: Alias of `/api/v1/admin/routing/rules/{id}`.
      x-roles: [admin]
      x-required-scopes: ["routing:write"]
      requestBody:
        $ref: '#/components/requestBodies/RoutingRulePatch'
      responses:
        '200':
          description: Routing rule updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdatedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [Admin Routing]
      operationId: adminDeleteRoutingRuleAlias
      summary: Delete a routing rule.
      description: Alias of `/api/v1/admin/routing/rules/{id}`.
      x-roles: [admin]
      x-required-scopes: ["routing:write"]
      responses:
        '200':
          description: Routing rule deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/news:
    get:
      tags: [News]
      operationId: adminListNews
      summary: List all company news.
      x-roles: [admin]
      x-required-scopes: ["settings:read"]
      responses:
        '200':
          description: News items.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    post:
      tags: [News]
      operationId: adminCreateNews
      summary: Create news.
      x-roles: [admin]
      x-required-scopes: ["settings:write"]
      requestBody:
        $ref: '#/components/requestBodies/NewsCreate'
      responses:
        '201':
          description: News created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IDResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/news/{id}:
    parameters:
      - $ref: '#/components/parameters/ID'
    patch:
      tags: [News]
      operationId: adminUpdateNews
      summary: Update news.
      x-roles: [admin]
      x-required-scopes: ["settings:write"]
      requestBody:
        $ref: '#/components/requestBodies/NewsPatch'
      responses:
        '200':
          description: News updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdatedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      tags: [News]
      operationId: adminDeleteNews
      summary: Delete news.
      x-roles: [admin]
      x-required-scopes: ["settings:write"]
      responses:
        '200':
          description: News deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/news/{id}/pin:
    parameters:
      - $ref: '#/components/parameters/ID'
    post:
      tags: [News]
      operationId: adminToggleNewsPin
      summary: Toggle news pinned state.
      x-roles: [admin]
      x-required-scopes: ["settings:write"]
      responses:
        '200':
          description: News pin state updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdatedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/postback-log:
    get:
      tags: [Admin Logs]
      operationId: adminListPostbackLog
      summary: List postback log rows.
      x-roles: [admin]
      x-required-scopes: ["logs:read"]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/Outcome'
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - $ref: '#/components/parameters/Search'
      responses:
        '200':
          description: Postback log rows.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PostbackLogListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/audit-log:
    get:
      tags: [Admin Logs]
      operationId: adminListAuditLog
      summary: List audit log rows.
      x-roles: [admin]
      x-required-scopes: ["logs:read"]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/EntityType'
        - $ref: '#/components/parameters/UserID'
        - $ref: '#/components/parameters/Search'
      responses:
        '200':
          description: Audit log rows.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditLogListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/login-history:
    get:
      tags: [Admin Logs]
      operationId: adminListLoginHistory
      summary: List login history rows.
      x-roles: [admin]
      x-required-scopes: ["logs:read"]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/UserID'
        - $ref: '#/components/parameters/Search'
      responses:
        '200':
          description: Login history rows.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginHistoryListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/top-publishers:
    get:
      tags: [Reports]
      operationId: adminTopPublishers
      summary: Get admin top publishers report.
      x-roles: [admin]
      x-required-scopes: ["reports:read"]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - $ref: '#/components/parameters/OfferSlug'
        - $ref: '#/components/parameters/ScopeQuery'
        - $ref: '#/components/parameters/StatusQuery'
        - $ref: '#/components/parameters/SortBy'
      responses:
        '200':
          description: Top publisher rows.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TopPublisherListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/fraud-report:
    get:
      tags: [Reports]
      operationId: adminFraudReport
      summary: Get admin fraud report.
      x-roles: [admin]
      x-required-scopes: ["reports:read"]
      parameters:
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Fraud report.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FraudResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /api/v1/admin/search/users:
    get:
      tags: [Partners]
      operationId: adminSearchUsers
      summary: Search company users.
      x-roles: [admin]
      x-required-scopes: ["partners:read"]
      parameters:
        - $ref: '#/components/parameters/Q'
        - $ref: '#/components/parameters/RoleQuery'
        - $ref: '#/components/parameters/StatusQuery'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: User search results.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchUserListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        default:
          $ref: '#/components/responses/ErrorResponse'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: OfferDaemon API key
      description: 'Use `Authorization: Bearer <api_key>`.'

  parameters:
    ID:
      name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    RuleID:
      name: rule_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    OverrideID:
      name: override_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    PublisherIDPath:
      name: publisher_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    Slug:
      name: slug
      in: path
      required: true
      schema:
        type: string
    Page:
      name: page
      in: query
      schema:
        type: integer
        minimum: 1
        default: 1
    PerPage:
      name: per_page
      in: query
      schema:
        type: integer
        enum: [25, 50, 100]
        default: 50
    Limit:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
    Q:
      name: q
      in: query
      schema:
        type: string
    Search:
      name: search
      in: query
      schema:
        type: string
    CategoryID:
      name: category_id
      in: query
      schema:
        type: string
        format: uuid
    OfferIDQuery:
      name: offer_id
      in: query
      schema:
        type: string
        format: uuid
    OfferSlug:
      name: offer
      in: query
      schema:
        type: string
    PublisherIDQuery:
      name: publisher_id
      in: query
      schema:
        type: string
        format: uuid
    PublisherSearch:
      name: publisher
      in: query
      schema:
        type: string
    BeneficiaryID:
      name: beneficiary_id
      in: query
      schema:
        type: string
        format: uuid
    BeneficiaryType:
      name: beneficiary_type
      in: query
      schema:
        type: string
        enum: [publisher, merchant, referral, all]
    ScopeQuery:
      name: scope
      in: query
      schema:
        type: string
        enum: [publisher, merchant, referral, my, all]
    StatusQuery:
      name: status
      in: query
      schema:
        type: string
    RoleQuery:
      name: role
      in: query
      schema:
        $ref: '#/components/schemas/Role'
    EventType:
      name: event_type
      in: query
      schema:
        type: string
    SettlementStatus:
      name: settlement_status
      in: query
      schema:
        $ref: '#/components/schemas/SettlementStatus'
    DateFrom:
      name: date_from
      in: query
      description: Inclusive start date. Report ranges default to 7 days and are limited to 90 days; fraud reports are limited to 14 days.
      schema:
        type: string
        format: date
    DateTo:
      name: date_to
      in: query
      description: Inclusive end date. Report ranges default to 7 days and are limited to 90 days; fraud reports are limited to 14 days.
      schema:
        type: string
        format: date
    Country:
      name: country
      in: query
      schema:
        type: string
        minLength: 2
        maxLength: 2
    DeviceType:
      name: device_type
      in: query
      schema:
        type: string
    Granularity:
      name: granularity
      in: query
      schema:
        type: string
        enum: [hourly, daily, monthly]
        default: daily
    GroupBy:
      name: group_by
      in: query
      schema:
        type: string
        enum: [offer, publisher, country, event_type, os, browser, isp, device_type, sub1, sub2, sub3, sub4, sub5, beneficiary_type]
    OS:
      name: os
      in: query
      schema:
        type: string
    Browser:
      name: browser
      in: query
      schema:
        type: string
    ISP:
      name: isp
      in: query
      schema:
        type: string
    SortBy:
      name: sort_by
      in: query
      description: |
        Sort field for top-publisher reports. Admin callers may use every
        listed value. Merchant callers may use `payout`, `conversions`,
        `clicks`, `cr`, or `unsettled`; financial admin fields (`revenue`,
        `profit`, and `roi`) are rejected for merchant callers.
      schema:
        type: string
        enum: [payout, conversions, clicks, revenue, cr, profit, roi, unsettled]
    FraudBucket:
      name: fraud_bucket
      in: query
      schema:
        type: string
    IsProxy:
      name: is_proxy
      in: query
      schema:
        type: string
        enum: ['true', 'false', '1', '0', yes, no]
    IsDatacenter:
      name: is_datacenter
      in: query
      schema:
        type: string
        enum: ['true', 'false', '1', '0', yes, no]
    LandingPageQuery:
      name: lp
      in: query
      description: Landing page slug. `landing_page` is accepted by the server as an alternate query/body field.
      schema:
        type: string
    Outcome:
      name: outcome
      in: query
      schema:
        type: string
    EntityType:
      name: entity_type
      in: query
      schema:
        type: string
    UserID:
      name: user_id
      in: query
      schema:
        type: string
        format: uuid
    Sub1:
      name: sub1
      in: query
      schema:
        type: string
    Sub2:
      name: sub2
      in: query
      schema:
        type: string
    Sub3:
      name: sub3
      in: query
      schema:
        type: string
    Sub4:
      name: sub4
      in: query
      schema:
        type: string
    Sub5:
      name: sub5
      in: query
      schema:
        type: string
    Sub6:
      name: sub6
      in: query
      schema:
        type: string
    Sub7:
      name: sub7
      in: query
      schema:
        type: string
    Sub8:
      name: sub8
      in: query
      schema:
        type: string
    Sub9:
      name: sub9
      in: query
      schema:
        type: string
    Sub10:
      name: sub10
      in: query
      schema:
        type: string

  requestBodies:
    OfferCreate:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/OfferCreateRequest'
    OfferPatch:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/OfferPatchRequest'
    BulkOfferToggle:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/BulkOfferToggleRequest'
    TrackingLink:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/TrackingLinkRequest'
    CategoryCreate:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CategoryCreateRequest'
    CategoryPatch:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CategoryPatchRequest'
    AdvertiserCreate:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AdvertiserCreateRequest'
    AdvertiserPatch:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AdvertiserPatchRequest'
    LandingPageCreate:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/LandingPageCreateRequest'
    LandingPagePatch:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/LandingPagePatchRequest'
    LandingPageSave:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/LandingPageSaveRequest'
    OfferCapsSave:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/OfferCapsSaveRequest'
    RateRulesSave:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RateRulesSaveRequest'
    PublisherOverrideUpsert:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublisherOverrideUpsertRequest'
    PartnerPatch:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PartnerPatchRequest'
    PartnerBulkStatus:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PartnerBulkStatusRequest'
    ConversionBulkStatus:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ConversionBulkStatusRequest'
    ConversionBulkIDs:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ConversionBulkIDsRequest'
    ManualConversion:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ManualConversionRequest'
    AdminPaymentGenerate:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AdminPaymentGenerateRequest'
    AdminPaymentApprove:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AdminPaymentApproveRequest'
    AdminPaymentPaid:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AdminPaymentPaidRequest'
    AdminPaymentNotes:
      required: false
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AdminPaymentNotesRequest'
    PaymentMethodCreate:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PaymentMethodCreateRequest'
    PaymentMethodPatch:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PaymentMethodPatchRequest'
    RedirectDomainCreate:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RedirectDomainCreateRequest'
    SettingsPatch:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SettingsPatchRequest'
    CustomFieldsPut:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CustomFieldsPutRequest'
    RoutingProfileCreate:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RoutingProfileCreateRequest'
    RoutingProfilePatch:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RoutingProfilePatchRequest'
    RoutingRuleCreate:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RoutingRuleCreateRequest'
    RoutingRulePatch:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RoutingRulePatchRequest'
    RoutingRuleOffers:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RoutingRuleOffersRequest'
    NewsCreate:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/NewsCreateRequest'
    NewsPatch:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/NewsPatchRequest'
    TicketCreate:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/TicketCreateRequest'
    TicketMessage:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/TicketMessageRequest'
    TicketStatus:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/TicketStatusRequest'
  responses:
    ErrorResponse:
      description: Error response.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
        X-RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    BadRequest:
      description: Request syntax, parameters, or payload validation failed.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: validation_error
              message: Request validation failed
              request_id: req_01JEXAMPLE
    Unauthorized:
      description: Bearer API key is missing, invalid, revoked, or expired.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: invalid_token
              message: API key is invalid
              request_id: req_01JEXAMPLE
    PaymentRequired:
      description: An admin write requires an eligible Enterprise plan outside an active trial.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: enterprise_plan_required
              message: OfferDaemon API writes require an Enterprise plan outside the trial period
              request_id: req_01JEXAMPLE
    Forbidden:
      description: The authenticated key role, scope, user, or company is not allowed to perform the operation.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: insufficient_scope
              message: API key does not have the required scope
              request_id: req_01JEXAMPLE
    NotFound:
      description: The requested resource does not exist in the authenticated company scope.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: not_found
              message: Resource not found
              request_id: req_01JEXAMPLE
    TooManyRequests:
      description: The authentication or per-key request rate limit was exceeded.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
        X-RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: rate_limited
              message: Rate limit exceeded
              request_id: req_01JEXAMPLE
    InternalServerError:
      description: Authentication or operation processing failed unexpectedly.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: internal_error
              message: Internal server error
              request_id: req_01JEXAMPLE
    NotImplemented:
      description: Endpoint is registered but intentionally not implemented yet.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'

  headers:
    RequestID:
      description: Request id generated from `X-Request-ID` or server time.
      schema:
        type: string
    RateLimitLimit:
      description: Current rate limit window size. Reads default to 600/min and writes default to 60/min.
      schema:
        type: integer
    RateLimitRemaining:
      description: Remaining requests in the current minute window.
      schema:
        type: integer
    RateLimitReset:
      description: Unix timestamp when the current rate limit window resets.
      schema:
        type: integer

  schemas:
    Role:
      type: string
      enum: [admin, publisher, merchant]
    Scope:
      type: string
      enum:
        - profile:read
        - profile:write
        - company:read
        - company:write
        - offers:read
        - offers:write
        - tracking_links:read
        - reports:read
        - clicks:read
        - conversions:read
        - conversions:write
        - payments:read
        - payments:write
        - partners:read
        - partners:write
        - tickets:read
        - tickets:write
        - settings:read
        - settings:write
        - routing:read
        - routing:write
        - logs:read
        - api_keys:read
        - api_keys:write
        - admin_full
    APIKeyPreset:
      type: string
      enum: [read, read_only, write, publisher_full, merchant_full, admin_read, admin_full]
    UserStatus:
      type: string
      enum: [approved, pending, rejected]
    ConversionStatus:
      type: string
      enum: [pending, approved, rejected]
    SettlementStatus:
      type: string
      enum: [unsettled, locked, settled]
    PaymentStatus:
      type: string
      enum: [requested, approved, processing, paid, rejected, cancelled]
    TicketStatus:
      type: string
      enum: [open, closed, resolved]

    Meta:
      type: object
      required: [page, per_page, total, total_pages]
      properties:
        page:
          type: integer
        per_page:
          type: integer
        total:
          type: integer
        total_pages:
          type: integer
    ErrorEnvelope:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, request_id]
          properties:
            code:
              type: string
              examples: [validation_error, invalid_token, enterprise_plan_required, insufficient_scope, not_found, rate_limited, internal_error]
            message:
              type: string
            request_id:
              type: string

    HealthResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [status, api_version, key_id, role]
          properties:
            status:
              type: string
              enum: [ok]
            api_version:
              type: string
              enum: [v1]
            key_id:
              type: string
            role:
              $ref: '#/components/schemas/Role'
    IDResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [id]
          properties:
            id:
              type: string
    UpdatedResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [updated]
          properties:
            updated:
              type: boolean
    DeletedResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [deleted]
          properties:
            deleted:
              type: boolean
    RevokedResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [revoked]
          properties:
            revoked:
              type: boolean
    CreatedBoolResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [created]
          properties:
            created:
              type: boolean
    UpdatedCountResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [updated]
          properties:
            updated:
              type: integer
    StatusResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [id, status]
          properties:
            id:
              type: string
            status:
              type: string
    DefaultResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [default]
          properties:
            default:
              type: boolean
    VerifiedResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [verified]
          properties:
            verified:
              type: boolean

    Profile:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        full_name:
          type: string
        role:
          $ref: '#/components/schemas/Role'
        status:
          $ref: '#/components/schemas/UserStatus'
        contact_phone:
          type: string
        contact_skype:
          type: string
        contact_telegram:
          type: string
        publisher_postback_url:
          type: string
        merchant_id:
          type: [string, 'null']
          format: uuid
        referral_id:
          type: [string, 'null']
          format: uuid
        payment_method_id:
          type: [string, 'null']
          format: uuid
        payment_details:
          type: string
          description: Returned only in self/profile and selected admin detail responses.
        ticket_signature:
          type: string
          description: Admin only.
        decimal_places:
          type: integer
          enum: [2, 4]
        ticket_notify:
          type: boolean
          description: Admin and merchant only.
        registration_ip:
          type: string
          description: Admin only.
        last_login_at:
          type: [string, 'null']
          format: date-time
          description: Admin only.
        admin_note:
          type: string
          description: Admin only.
        audience_sync_excluded:
          type: boolean
          description: Admin only.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ProfileResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/Profile'
    ProfilePatchRequest:
      type: object
      additionalProperties: false
      properties:
        full_name:
          type: string
        contact_phone:
          type: string
        contact_skype:
          type: string
        contact_telegram:
          type: string
        publisher_postback_url:
          type: string
        payment_method_id:
          type: string
          format: uuid
        payment_details:
          type: string
        ticket_signature:
          type: string
        decimal_places:
          type: integer
          enum: [2, 4]
        ticket_notify:
          type: boolean

    Company:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        custom_domain:
          type: [string, 'null']
        currency_symbol:
          type: string
        homepage_url:
          type: string
        logo_url:
          type: string
        logo_dark_url:
          type: string
        favicon_url:
          type: string
        slogan:
          type: string
        timezone:
          type: string
        merchant_enabled:
          type: boolean
        ref_enabled:
          type: boolean
        registration_open:
          type: boolean
        news_banner_enabled:
          type: boolean
        merchant_share_percent:
          type: integer
          description: Admin only.
        ref_share_percent:
          type: integer
          description: Admin only.
        merchant_commission_base:
          type: string
          enum: [payout, revenue, margin]
          description: Admin only.
        ref_commission_base:
          type: string
          enum: [payout, revenue, margin]
          description: Admin only.
        merchant_publisher_privacy:
          type: boolean
          description: Admin only.
        geo_targeting_enabled:
          type: boolean
          description: Admin only.
        offer_caps_enabled:
          type: boolean
          description: Admin only.
        custom_fields_enabled:
          type: boolean
          description: Admin only.
        offer_field_defs:
          type: array
          items:
            $ref: '#/components/schemas/OfferFieldDef'
          description: Admin only.
        dust_threshold:
          type: number
          description: Admin only.
        dust_action:
          type: string
          enum: [ignore, zero_payout]
          description: Admin only.
        plan:
          type: string
          description: Admin only.
    CompanyResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/Company'

    APIKey:
      type: object
      properties:
        id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        name:
          type: string
        key_prefix:
          type: string
        scopes:
          type: array
          items:
            $ref: '#/components/schemas/Scope'
        last_used_at:
          type: [string, 'null']
          format: date-time
        last_used_ip:
          type: [string, 'null']
        expires_at:
          type: [string, 'null']
          format: date-time
        revoked_at:
          type: [string, 'null']
          format: date-time
        revoked:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    APIKeyCreated:
      allOf:
        - $ref: '#/components/schemas/APIKey'
        - type: object
          properties:
            token:
              type: string
              description: Raw API key token. Returned once at creation.
    APIKeyListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/APIKey'
    APIKeyCreatedResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/APIKeyCreated'
    APIKeyCreateRequest:
      type: object
      additionalProperties: false
      required: [name]
      properties:
        name:
          type: string
        preset:
          $ref: '#/components/schemas/APIKeyPreset'
        scopes:
          type: array
          items:
            $ref: '#/components/schemas/Scope'
        expires_at:
          type: string
          format: date-time

    Category:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        sort_order:
          type: integer
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    CategoryListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Category'
    CategoryResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/Category'
    CategoryCreateRequest:
      type: object
      additionalProperties: false
      required: [name]
      properties:
        name:
          type: string
        slug:
          type: string
        sort_order:
          type: integer
    CategoryPatchRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
        slug:
          type: string
        sort_order:
          type: integer

    Advertiser:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        status:
          type: string
          enum: [active, inactive]
        contact_email:
          type: string
        notes:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    AdvertiserListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Advertiser'
    AdvertiserResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/Advertiser'
    AdvertiserCreateRequest:
      type: object
      additionalProperties: false
      required: [name]
      properties:
        name:
          type: string
        slug:
          type: string
        status:
          type: string
          enum: [active, inactive]
        contact_email:
          type: string
        notes:
          type: string
    AdvertiserPatchRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
        status:
          type: string
          enum: [active, inactive]
        contact_email:
          type: string
        notes:
          type: string

    Offer:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        description:
          type: string
        active:
          type: boolean
        category_id:
          type: string
        category_name:
          type: string
        payout_display:
          type: string
        allow_recurring_postback:
          type: boolean
        is_smartlink:
          type: boolean
        is_public:
          type: boolean
        is_featured:
          type: boolean
        countries:
          type: array
          items:
            type: string
        extra_fields:
          type: object
          additionalProperties: true
        landing_pages:
          type: array
          items:
            $ref: '#/components/schemas/LandingPage'
        advertiser_id:
          type: [string, 'null']
          format: uuid
          description: Admin only.
        advertiser_name:
          type: string
          description: Admin only.
        url:
          type: string
          description: Admin only.
        fallback_url:
          type: string
          description: Admin only.
        revenue_source:
          type: string
          description: Admin only.
        default_rate:
          type: number
          description: Admin only.
        auto_approve_postback:
          type: boolean
          description: Admin only.
        allow_refunds:
          type: boolean
          description: Admin only.
        internal_note:
          type: string
          description: Admin only.
        adv_revenue_source:
          type: string
          description: Admin only.
        advertiser_payout:
          type: number
          description: Admin only.
        redirect_domain_id:
          type: [string, 'null']
          format: uuid
          description: Admin only.
        routing_profile_id:
          type: [string, 'null']
          format: uuid
          description: Admin only.
        rate_rules:
          type: array
          items:
            $ref: '#/components/schemas/RateRule'
          description: Admin only.
        caps:
          type: array
          items:
            $ref: '#/components/schemas/OfferCap'
          description: Admin only.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    OfferListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Offer'
        meta:
          $ref: '#/components/schemas/Meta'
    OfferResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/Offer'
    OfferCreateRequest:
      type: object
      additionalProperties: false
      required: [name, url]
      properties:
        name:
          type: string
        slug:
          type: string
        advertiser_id:
          type: string
          format: uuid
        category_id:
          type: string
          format: uuid
        description:
          type: string
        url:
          type: string
        fallback_url:
          type: string
        active:
          type: boolean
        revenue_source:
          type: string
        default_rate:
          type: number
        payout_display:
          type: string
        allow_recurring_postback:
          type: boolean
        is_smartlink:
          type: boolean
        auto_approve_postback:
          type: boolean
        allow_refunds:
          type: boolean
        is_public:
          type: boolean
        is_featured:
          type: boolean
        internal_note:
          type: string
        adv_revenue_source:
          type: string
        advertiser_payout:
          type: number
        redirect_domain_id:
          type: string
          format: uuid
        routing_profile_id:
          type: string
          format: uuid
        extra_fields:
          type: object
          additionalProperties: true
    OfferPatchRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
        description:
          type: string
        url:
          type: string
        fallback_url:
          type: string
        active:
          type: boolean
        is_public:
          type: boolean
        is_featured:
          type: boolean
        payout_display:
          type: string
        revenue_source:
          type: string
        default_rate:
          type: number
        adv_revenue_source:
          type: string
        advertiser_payout:
          type: number
        internal_note:
          type: string
    BulkOfferToggleRequest:
      type: object
      additionalProperties: false
      required: [ids, active]
      properties:
        ids:
          type: array
          minItems: 1
          maxItems: 500
          items:
            type: string
            format: uuid
        active:
          type: boolean

    LandingPage:
      type: object
      properties:
        id:
          type: string
          format: uuid
        offer_id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        slug:
          type: string
        name:
          type: string
        url:
          type: string
          description: Admin only.
        active:
          type: boolean
        note:
          type: string
          description: Admin only.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    LandingPageListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/LandingPage'
    LandingPageResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/LandingPage'
    LandingPageCreateRequest:
      type: object
      additionalProperties: false
      required: [name, url]
      properties:
        name:
          type: string
        url:
          type: string
        active:
          type: boolean
          default: true
        note:
          type: string
    LandingPagePatchRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
        url:
          type: string
        active:
          type: boolean
        note:
          type: string
    LandingPageSaveRequest:
      type: object
      additionalProperties: false
      properties:
        landing_pages:
          type: array
          items:
            $ref: '#/components/schemas/LandingPageReplaceItem'
    LandingPageReplaceItem:
      type: object
      additionalProperties: false
      required: [name, url]
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        name:
          type: string
        url:
          type: string
        active:
          type: boolean
        note:
          type: string

    OfferCap:
      type: object
      properties:
        id:
          type: string
          format: uuid
        offer_id:
          type: string
          format: uuid
        cap_type:
          type: string
          enum: [daily_conversions, total_conversions, daily_revenue]
        cap_value:
          type: number
        current_value:
          type: number
        reset_at:
          type: [string, 'null']
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    OfferCapListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/OfferCap'
    OfferCapsSaveRequest:
      type: object
      additionalProperties: false
      properties:
        caps:
          type: array
          items:
            $ref: '#/components/schemas/OfferCapReplaceItem'
    OfferCapReplaceItem:
      type: object
      additionalProperties: false
      required: [cap_type, cap_value]
      properties:
        cap_type:
          type: string
          enum: [daily_conversions, total_conversions, daily_revenue]
        cap_value:
          type: number
          exclusiveMinimum: 0

    RateRule:
      type: object
      properties:
        id:
          type: string
          format: uuid
        offer_id:
          type: string
          format: uuid
        conditions:
          type: object
          additionalProperties:
            type: string
        publisher_rate:
          type: number
        advertiser_rate:
          type: number
    RateRuleListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/RateRule'
    RateRulesSaveRequest:
      type: object
      additionalProperties: false
      properties:
        rate_rules:
          type: array
          items:
            $ref: '#/components/schemas/RateRuleReplaceItem'
    RateRuleReplaceItem:
      type: object
      additionalProperties: false
      properties:
        conditions:
          type: object
          additionalProperties:
            type: string
        publisher_rate:
          type: number
          minimum: 0
        advertiser_rate:
          type: number
          minimum: 0

    PublisherOverride:
      type: object
      properties:
        id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        offer_id:
          type: string
          format: uuid
        offer_name:
          type: string
        publisher_id:
          type: string
          format: uuid
        publisher_name:
          type: string
        publisher_email:
          type: string
        boost_percent:
          type: number
        note:
          type: string
        created_at:
          type: string
    PublisherOverrideListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/PublisherOverride'
    PublisherOverrideUpsertRequest:
      type: object
      additionalProperties: false
      required: [boost_percent]
      properties:
        boost_percent:
          type: number
          minimum: -99
          maximum: 10000
        note:
          type: string
    PublisherOverrideUpsertResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          properties:
            publisher_id:
              type: string
              format: uuid
            boost_percent:
              type: number
            note:
              type: string
            old_boost_percent:
              type: number
            mode:
              type: string
              enum: [create, update]

    TrackingLinkRequest:
      type: object
      additionalProperties: false
      properties:
        publisher_id:
          type: string
          format: uuid
          description: Required for admin and merchant keys. Ignored for publisher keys.
        landing_page:
          type: string
        lp:
          type: string
        sub1:
          type: string
        sub2:
          type: string
        sub3:
          type: string
        sub4:
          type: string
        sub5:
          type: string
        sub6:
          type: string
        sub7:
          type: string
        sub8:
          type: string
        sub9:
          type: string
        sub10:
          type: string
    TrackingLinkResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [url, domain, offer_id, offer_slug, publisher_id]
          properties:
            url:
              type: string
              format: uri
            domain:
              type: string
            offer_id:
              type: string
              format: uuid
            offer_slug:
              type: string
            publisher_id:
              type: string
              format: uuid

    NewsItem:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        body:
          type: string
        is_pinned:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    NewsListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/NewsItem'
    NewsCreateRequest:
      type: object
      additionalProperties: false
      properties:
        title:
          type: string
        body:
          type: string
        is_pinned:
          type: boolean
    NewsPatchRequest:
      type: object
      additionalProperties: false
      properties:
        title:
          type: string
        body:
          type: string
        is_pinned:
          type: boolean

    Partner:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
        full_name:
          type: string
        role:
          $ref: '#/components/schemas/Role'
        status:
          $ref: '#/components/schemas/UserStatus'
        contact_phone:
          type: string
        contact_skype:
          type: string
        contact_telegram:
          type: string
        merchant_id:
          type: [string, 'null']
          format: uuid
        merchant_name:
          type: string
        referral_id:
          type: [string, 'null']
          format: uuid
        referral_name:
          type: string
        payment_method:
          type: string
        registration_ip:
          type: string
        last_login_at:
          type: string
        admin_note:
          type: string
        email_verified:
          type: boolean
        audience_excluded:
          type: boolean
        created_at:
          type: string
        updated_at:
          type: string
    PartnerListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Partner'
    PartnerPatchRequest:
      type: object
      additionalProperties: false
      properties:
        role:
          $ref: '#/components/schemas/Role'
        status:
          $ref: '#/components/schemas/UserStatus'
        merchant_id:
          type: string
          format: uuid
        referral_id:
          type: string
          format: uuid
        redirect_domain_id:
          type: string
          format: uuid
        full_name:
          type: string
        admin_note:
          type: string
        audience_sync_excluded:
          type: boolean
    PartnerBulkStatusRequest:
      type: object
      additionalProperties: false
      required: [ids, status]
      properties:
        ids:
          type: array
          minItems: 1
          maxItems: 500
          uniqueItems: true
          items:
            type: string
            format: uuid
        status:
          $ref: '#/components/schemas/UserStatus'
    PartnerLink:
      type: object
      properties:
        offer_id:
          type: string
          format: uuid
        offer_slug:
          type: string
        offer_name:
          type: string
        domain:
          type: string
        url:
          type: string
          format: uri
    PartnerLinkListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/PartnerLink'
    MerchantPublisher:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
        full_name:
          type: string
        status:
          $ref: '#/components/schemas/UserStatus'
        contact_phone:
          type: string
        contact_skype:
          type: string
        contact_telegram:
          type: string
        created_at:
          type: string
        last_login_at:
          type: string
    MerchantPublisherListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/MerchantPublisher'

    Conversion:
      type: object
      properties:
        id:
          type: string
          format: uuid
        postback_event_id:
          type: string
          format: uuid
        created_at:
          type: string
        event_type:
          type: string
        payout:
          type: number
        currency:
          type: string
        status:
          $ref: '#/components/schemas/ConversionStatus'
        settlement_status:
          $ref: '#/components/schemas/SettlementStatus'
        click_id:
          type: string
        transaction_id:
          type: string
        offer_name:
          type: string
        country:
          type: string
        city:
          type: string
        device_type:
          type: string
        os:
          type: string
        browser:
          type: string
        traffic_source:
          type: string
        campaign_id:
          type: string
        campaign_name:
          type: string
        external_click_id:
          type: string
        keyword:
          type: string
        creative_id:
          type: string
        zone_id:
          type: string
        site_id:
          type: string
        sub1:
          type: string
        sub2:
          type: string
        sub3:
          type: string
        sub4:
          type: string
        sub5:
          type: string
        advertiser_revenue:
          type: number
          description: Admin only.
        beneficiary_type:
          type: string
          description: Admin only.
        beneficiary_email:
          type: string
          description: Admin only.
        publisher_email:
          type: string
          description: Admin and merchant only.
        publisher_name:
          type: string
          description: Admin and merchant only.
        ip_address:
          type: string
          description: Admin only.
        fraud_score:
          type: integer
          description: Admin only.
        cost:
          type: string
          description: Admin only.
    ConversionListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Conversion'
        meta:
          $ref: '#/components/schemas/Meta'
    ConversionBulkStatusRequest:
      type: object
      additionalProperties: false
      required: [ids, status]
      properties:
        ids:
          type: array
          minItems: 1
          maxItems: 500
          uniqueItems: true
          items:
            type: string
            format: uuid
        status:
          type: string
          enum: [approved, rejected]
    ConversionBulkIDsRequest:
      type: object
      additionalProperties: false
      required: [ids]
      properties:
        ids:
          type: array
          minItems: 1
          maxItems: 500
          uniqueItems: true
          items:
            type: string
            format: uuid
    BulkConversionStatusResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [updated, skipped]
          properties:
            updated:
              type: integer
            skipped:
              type: integer
    ManualConversionRequest:
      type: object
      additionalProperties: false
      required: [publisher_id, offer_id, event_type, amount]
      properties:
        publisher_id:
          type: string
          format: uuid
        offer_id:
          type: string
          format: uuid
        event_type:
          type: string
        amount:
          type: number
          minimum: 0
        advertiser_revenue:
          type: number
          minimum: 0
        currency:
          type: string
          default: USD
        status:
          $ref: '#/components/schemas/ConversionStatus'
        admin_note:
          type: string
    ManualConversionResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/ManualConversionResult'
    PostbackEvent:
      type: object
      additionalProperties: false
      required: [id, company_id, click_id, event_type, amount, currency, transaction_id, offer_id, publisher_id, ip_address, raw_query, processed, error_message, received_at, country]
      properties:
        id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        click_id:
          type: [string, 'null']
        event_type:
          type: string
        amount:
          type: [number, 'null']
        currency:
          type: string
        transaction_id:
          type: [string, 'null']
        offer_id:
          type: [string, 'null']
          format: uuid
        publisher_id:
          type: [string, 'null']
          format: uuid
        ip_address:
          type: [string, 'null']
        raw_query:
          type: [string, 'null']
        processed:
          type: boolean
        error_message:
          type: [string, 'null']
        received_at:
          type: string
          format: date-time
        country:
          type: [string, 'null']
    ManualConversionRecord:
      type: object
      additionalProperties: false
      required: [id, company_id, postback_event_id, beneficiary_type, beneficiary_id, payout, advertiser_revenue, currency, offer_id, publisher_id, click_id, event_type, status, settlement_status, settled_at, created_at]
      properties:
        id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        postback_event_id:
          type: string
          format: uuid
        beneficiary_type:
          type: string
        beneficiary_id:
          type: string
          format: uuid
        payout:
          type: number
        advertiser_revenue:
          type: number
        currency:
          type: string
        offer_id:
          type: [string, 'null']
          format: uuid
        publisher_id:
          type: [string, 'null']
          format: uuid
        click_id:
          type: [string, 'null']
        event_type:
          type: string
        status:
          type: string
        settlement_status:
          type: string
        settled_at:
          type: [string, 'null']
          format: date-time
        created_at:
          type: string
          format: date-time
    ManualConversionResult:
      type: object
      additionalProperties: false
      required: [event, conversions, outcome, company_id]
      properties:
        event:
          oneOf:
            - $ref: '#/components/schemas/PostbackEvent'
            - type: 'null'
        conversions:
          type: array
          items:
            $ref: '#/components/schemas/ManualConversionRecord'
        outcome:
          type: string
        company_id:
          type: string
          format: uuid

    Click:
      type: object
      properties:
        id:
          type: string
        received_at:
          type: string
        offer:
          type: string
        offer_name:
          type: string
        country:
          type: string
        region:
          type: string
        city:
          type: string
        device_type:
          type: string
        device_brand:
          type: string
        os:
          type: string
        browser:
          type: string
        traffic_source:
          type: string
        campaign_id:
          type: string
        campaign_name:
          type: string
        external_click_id:
          type: string
        sub1:
          type: string
        sub2:
          type: string
        sub3:
          type: string
        sub4:
          type: string
        sub5:
          type: string
        publisher_id:
          type: string
          description: Admin and merchant only.
        publisher_email:
          type: string
          description: Admin and merchant only.
        ip_address:
          type: string
          description: Admin only.
        isp:
          type: string
          description: Admin only.
        asn:
          type: string
          description: Admin only.
        fraud_score:
          type: integer
          description: Admin only.
        is_proxy:
          type: boolean
          description: Admin only.
        is_datacenter:
          type: boolean
          description: Admin only.
        is_bot:
          type: boolean
          description: Admin only.
        blocked_reason:
          type: string
          description: Admin only.
        host:
          type: string
          description: Admin only.
        http_status:
          type: integer
          description: Admin only.
    ClickListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Click'
        meta:
          $ref: '#/components/schemas/Meta'

    PerformanceBucket:
      type: object
      properties:
        period:
          type: string
        label:
          type: string
        clicks:
          type: integer
        conversions:
          type: integer
        payout:
          type: number
        cr:
          type: number
        epc:
          type: number
        avg_payout:
          type: number
        epm:
          type: number
        c_ratio:
          type: string
        revenue:
          type: number
          description: Admin only.
        profit:
          type: number
          description: Admin only.
        roi:
          type: number
          description: Admin only.
        avg_revenue:
          type: number
          description: Admin only.
    Performance:
      type: object
      properties:
        buckets:
          type: array
          items:
            $ref: '#/components/schemas/PerformanceBucket'
        chart_buckets:
          type: array
          items:
            $ref: '#/components/schemas/PerformanceBucket'
        totals:
          $ref: '#/components/schemas/PerformanceBucket'
    PerformanceResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/Performance'
    TopPublisher:
      type: object
      additionalProperties: false
      required: [id, name, clicks, conversions, unsettled_conversions, payout, cr, epc]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        clicks:
          type: integer
        conversions:
          type: integer
        unsettled_conversions:
          type: integer
        payout:
          type: number
        cr:
          type: number
        epc:
          type: number
        revenue:
          type: number
          description: Admin only.
        profit:
          type: number
          description: Admin only.
        roi:
          type: number
          description: Admin only.
    TopPublisherListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/TopPublisher'
        meta:
          $ref: '#/components/schemas/Meta'
    FraudSummary:
      type: object
      additionalProperties: false
      required: [total_clicks, proxy_clicks, bot_clicks, dc_clicks, flagged_clicks, total_conversions, flagged_conversions, proxy_conversions, bot_conversions, dc_conversions, avg_fraud_score, avg_conv_fraud_score, proxy_pct, bot_pct, dc_pct, flagged_pct, conv_flagged_pct]
      properties:
        total_clicks:
          type: integer
        proxy_clicks:
          type: integer
        bot_clicks:
          type: integer
        dc_clicks:
          type: integer
        flagged_clicks:
          type: integer
        total_conversions:
          type: integer
        flagged_conversions:
          type: integer
        proxy_conversions:
          type: integer
        bot_conversions:
          type: integer
        dc_conversions:
          type: integer
        avg_fraud_score:
          type: number
        avg_conv_fraud_score:
          type: number
        proxy_pct:
          type: number
        bot_pct:
          type: number
        dc_pct:
          type: number
        flagged_pct:
          type: number
        conv_flagged_pct:
          type: number
    FraudPublisher:
      type: object
      additionalProperties: false
      required: [id, name, total_clicks, flagged_clicks, flagged_pct, avg_fraud_score, proxy_clicks, bot_clicks, dc_clicks, total_conversions, flagged_conversions, proxy_conversions, bot_conversions, dc_conversions, avg_conv_fraud_score, conv_flagged_pct, cr]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        total_clicks:
          type: integer
        flagged_clicks:
          type: integer
        flagged_pct:
          type: number
        avg_fraud_score:
          type: number
        proxy_clicks:
          type: integer
        bot_clicks:
          type: integer
        dc_clicks:
          type: integer
        total_conversions:
          type: integer
        flagged_conversions:
          type: integer
        proxy_conversions:
          type: integer
        bot_conversions:
          type: integer
        dc_conversions:
          type: integer
        avg_conv_fraud_score:
          type: number
        conv_flagged_pct:
          type: number
        cr:
          type: number
    FraudOffer:
      type: object
      additionalProperties: false
      required: [id, name, slug, total_clicks, flagged_clicks, flagged_pct, avg_fraud_score, proxy_clicks, bot_clicks, dc_clicks, total_conversions, flagged_conversions, proxy_conversions, bot_conversions, dc_conversions, avg_conv_fraud_score, conv_flagged_pct, cr]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        total_clicks:
          type: integer
        flagged_clicks:
          type: integer
        flagged_pct:
          type: number
        avg_fraud_score:
          type: number
        proxy_clicks:
          type: integer
        bot_clicks:
          type: integer
        dc_clicks:
          type: integer
        total_conversions:
          type: integer
        flagged_conversions:
          type: integer
        proxy_conversions:
          type: integer
        bot_conversions:
          type: integer
        dc_conversions:
          type: integer
        avg_conv_fraud_score:
          type: number
        conv_flagged_pct:
          type: number
        cr:
          type: number
    FraudDaily:
      type: object
      additionalProperties: false
      required: [date, total_clicks, flagged_clicks, flagged_pct, avg_fraud_score, proxy_clicks, bot_clicks, dc_clicks, total_conversions, flagged_conversions, proxy_conversions, bot_conversions, dc_conversions, avg_conv_fraud_score]
      properties:
        date:
          type: string
          format: date
        total_clicks:
          type: integer
        flagged_clicks:
          type: integer
        flagged_pct:
          type: number
        avg_fraud_score:
          type: number
        proxy_clicks:
          type: integer
        bot_clicks:
          type: integer
        dc_clicks:
          type: integer
        total_conversions:
          type: integer
        flagged_conversions:
          type: integer
        proxy_conversions:
          type: integer
        bot_conversions:
          type: integer
        dc_conversions:
          type: integer
        avg_conv_fraud_score:
          type: number
    FraudResult:
      type: object
      additionalProperties: false
      required: [summary, publishers, offers, daily]
      properties:
        summary:
          $ref: '#/components/schemas/FraudSummary'
        publishers:
          type: array
          items:
            $ref: '#/components/schemas/FraudPublisher'
        offers:
          type: array
          items:
            $ref: '#/components/schemas/FraudOffer'
        daily:
          type: array
          items:
            $ref: '#/components/schemas/FraudDaily'
    FraudResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/FraudResult'

    Payment:
      type: object
      properties:
        id:
          type: string
          format: uuid
        beneficiary_id:
          type: string
          format: uuid
        amount_requested:
          type: number
        amount_approved:
          type: [number, 'null']
        fee:
          type: number
        amount_sent:
          type: [number, 'null']
        currency:
          type: string
        payment_method_id:
          type: [string, 'null']
          format: uuid
        payment_method_name:
          type: string
        payment_details:
          type: string
          description: Detail endpoint only.
        status:
          $ref: '#/components/schemas/PaymentStatus'
        conversion_count:
          type: integer
        paid_at:
          type: [string, 'null']
          format: date-time
        period_start:
          type: [string, 'null']
          format: date-time
        period_end:
          type: [string, 'null']
          format: date-time
        beneficiary_email:
          type: string
          description: Admin only.
        beneficiary_name:
          type: string
          description: Admin only.
        notes:
          type: string
          description: Admin only.
        created_by:
          type: [string, 'null']
          format: uuid
          description: Admin only.
        approved_by:
          type: [string, 'null']
          format: uuid
          description: Admin only.
        conversions:
          type: array
          items:
            $ref: '#/components/schemas/PaymentConversion'
          description: Detail endpoint only.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    PaymentConversion:
      type: object
      properties:
        id:
          type: string
          format: uuid
        event_type:
          type: string
        payout:
          type: number
        currency:
          type: string
        status:
          $ref: '#/components/schemas/ConversionStatus'
        settlement_status:
          $ref: '#/components/schemas/SettlementStatus'
        created_at:
          type: string
        offer_name:
          type: string
        publisher_name:
          type: string
          description: Admin only.
    PaymentListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Payment'
        meta:
          $ref: '#/components/schemas/Meta'
    PaymentResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/Payment'
    BalanceResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [beneficiary_id, balance]
          properties:
            beneficiary_id:
              type: string
              format: uuid
            balance:
              type: number
    AdminPaymentGenerateRequest:
      type: object
      additionalProperties: false
      required: [beneficiary_id, payment_method_id, payment_details]
      properties:
        beneficiary_id:
          type: string
          format: uuid
        payment_method_id:
          type: string
          format: uuid
        payment_details:
          type: string
        notes:
          type: string
    AdminPaymentApproveRequest:
      type: object
      additionalProperties: false
      properties:
        amount_approved:
          type: number
          exclusiveMinimum: 0
        fee:
          type: number
          minimum: 0
          description: Must be less than amount_approved.
        notes:
          type: string
    AdminPaymentPaidRequest:
      type: object
      additionalProperties: false
      required: [amount_sent]
      properties:
        amount_sent:
          type: number
          exclusiveMinimum: 0
          description: Actual positive amount sent. It may differ from amount_approved minus fee.
        notes:
          type: string
    AdminPaymentNotesRequest:
      type: object
      additionalProperties: false
      properties:
        notes:
          type: string

    PaymentMethod:
      type: object
      properties:
        id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
        minimum:
          type: number
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    PaymentMethodListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/PaymentMethod'
    PaymentMethodResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/PaymentMethod'
    PaymentMethodCreateRequest:
      type: object
      additionalProperties: false
      required: [name]
      properties:
        name:
          type: string
        description:
          type: string
        minimum:
          type: number
    PaymentMethodPatchRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
        description:
          type: string
        minimum:
          type: number
        is_active:
          type: boolean

    Ticket:
      type: object
      properties:
        id:
          type: string
          format: uuid
        publisher_id:
          type: string
          format: uuid
        subject:
          type: string
        description:
          type: string
        status:
          $ref: '#/components/schemas/TicketStatus'
        created_by_role:
          $ref: '#/components/schemas/Role'
        created_by_id:
          type: [string, 'null']
          format: uuid
        last_response_at:
          type: [string, 'null']
          format: date-time
        closed_at:
          type: [string, 'null']
          format: date-time
        message_count:
          type: integer
        created_by_name:
          type: string
        last_message_preview:
          type: string
        is_unread:
          type: boolean
        needs_reply:
          type: boolean
        publisher_name:
          type: string
          description: Admin and merchant only.
        publisher_email:
          type: string
          description: Admin and merchant only.
        messages:
          type: array
          items:
            $ref: '#/components/schemas/TicketMessage'
          description: Detail endpoint only.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    TicketMessage:
      type: object
      properties:
        id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        sender_role:
          $ref: '#/components/schemas/Role'
        sender_name:
          type: string
        message:
          type: string
        created_at:
          type: string
          format: date-time
    TicketListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Ticket'
        meta:
          $ref: '#/components/schemas/Meta'
    TicketResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/Ticket'
    TicketCreatedResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [id, subject]
          properties:
            id:
              type: string
              format: uuid
            subject:
              type: string
    TicketCreateRequest:
      type: object
      additionalProperties: false
      required: [subject, description]
      properties:
        publisher_id:
          type: string
          format: uuid
          description: Required for admin and merchant-created tickets.
        subject:
          type: string
          maxLength: 200
        description:
          type: string
          maxLength: 10000
    TicketMessageRequest:
      type: object
      additionalProperties: false
      required: [message]
      properties:
        message:
          type: string
          maxLength: 10000
    TicketStatusRequest:
      type: object
      additionalProperties: false
      required: [status]
      properties:
        status:
          $ref: '#/components/schemas/TicketStatus'

    RedirectDomain:
      type: object
      properties:
        id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        domain:
          type: string
        is_default:
          type: boolean
        is_system:
          type: boolean
        cname_verified:
          type: boolean
        created_at:
          type: string
          format: date-time
    RedirectDomainListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/RedirectDomain'
    RedirectDomainResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/RedirectDomain'
    RedirectDomainCreateRequest:
      type: object
      additionalProperties: false
      required: [domain]
      properties:
        domain:
          type: string

    SettingsPatchRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
        currency_symbol:
          type: string
        homepage_url:
          type: string
        slogan:
          type: string
        redirect_domain:
          type: string
        custom_domain:
          type: string
        timezone:
          type: string
        merchant_publisher_privacy:
          type: boolean
        geo_targeting_enabled:
          type: boolean
        offer_caps_enabled:
          type: boolean
        news_banner_enabled:
          type: boolean
    OfferFieldDef:
      type: object
      additionalProperties: false
      properties:
        key:
          type: string
        label:
          type: string
        type:
          type: string
        options:
          type: array
          items:
            type: string
        description:
          type: string
        visible_to:
          type: array
          items:
            type: string
        required:
          type: boolean
        sort_order:
          type: integer
        min:
          type: [number, 'null']
        max:
          type: [number, 'null']
    CustomFieldsPutRequest:
      type: object
      additionalProperties: false
      properties:
        custom_fields_enabled:
          type: boolean
        offer_field_defs:
          type: array
          items:
            $ref: '#/components/schemas/OfferFieldDef'
    CustomFieldsResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          properties:
            custom_fields_enabled:
              type: boolean
            offer_field_defs:
              type: array
              items:
                $ref: '#/components/schemas/OfferFieldDef'
    AudienceTestResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          properties:
            ok:
              type: boolean
            provider:
              type: string
    AudienceSyncQueuedResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          properties:
            status:
              type: string
              enum: [pending]
    AudienceSyncStatusResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          additionalProperties: false
          required: [status]
          properties:
            status:
              type: string
              enum: [unavailable, idle, pending, done, error]
            total:
              type: integer
            upserted:
              type: integer
            removed:
              type: integer
            error:
              type: string
            at:
              type: string
              format: date-time

    RoutingProfile:
      type: object
      properties:
        id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
        rules:
          type: array
          items:
            $ref: '#/components/schemas/RoutingRule'
        rule_count:
          type: integer
        offer_count:
          type: integer
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    RoutingProfileListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/RoutingProfile'
    RoutingProfileResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/RoutingProfile'
    RoutingProfileCreateRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
        description:
          type: string
    RoutingProfilePatchRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
        description:
          type: string
    RoutingRule:
      type: object
      properties:
        id:
          type: string
          format: uuid
        profile_id:
          type: string
          format: uuid
        name:
          type: string
        priority:
          type: integer
        countries:
          type: array
          items:
            type: string
        device_types:
          type: array
          items:
            type: string
        os_list:
          type: array
          items:
            type: string
        browsers:
          type: array
          items:
            type: string
        hour_from:
          type: [integer, 'null']
          minimum: 0
          maximum: 23
        hour_to:
          type: [integer, 'null']
          minimum: 0
          maximum: 23
        active:
          type: boolean
        offers:
          type: array
          items:
            $ref: '#/components/schemas/RoutingRuleOffer'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    RoutingRuleOffer:
      type: object
      properties:
        id:
          type: string
          format: uuid
        rule_id:
          type: string
          format: uuid
        offer_id:
          type: string
          format: uuid
        offer_name:
          type: string
        offer_slug:
          type: string
        weight:
          type: integer
          minimum: 1
    RoutingRuleOfferRequest:
      type: object
      additionalProperties: false
      required: [offer_id, weight]
      properties:
        offer_id:
          type: string
          format: uuid
        weight:
          type: integer
          minimum: 1
    RoutingRuleCreateRequest:
      type: object
      additionalProperties: false
      required: [name]
      properties:
        name:
          type: string
        priority:
          type: integer
        countries:
          type: array
          items:
            type: string
        device_types:
          type: array
          items:
            type: string
        os_list:
          type: array
          items:
            type: string
        browsers:
          type: array
          items:
            type: string
        hour_from:
          type: integer
          minimum: 0
          maximum: 23
        hour_to:
          type: integer
          minimum: 0
          maximum: 23
        active:
          type: boolean
        offers:
          type: array
          items:
            $ref: '#/components/schemas/RoutingRuleOfferRequest'
    RoutingRulePatchRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
        priority:
          type: integer
        countries:
          type: array
          items:
            type: string
        device_types:
          type: array
          items:
            type: string
        os_list:
          type: array
          items:
            type: string
        browsers:
          type: array
          items:
            type: string
        hour_from:
          type: [integer, 'null']
          minimum: 0
          maximum: 23
        hour_to:
          type: [integer, 'null']
          minimum: 0
          maximum: 23
        active:
          type: boolean
        offers:
          type: array
          items:
            $ref: '#/components/schemas/RoutingRuleOfferRequest'
    RoutingRuleOffersRequest:
      type: object
      additionalProperties: false
      properties:
        offers:
          type: array
          items:
            $ref: '#/components/schemas/RoutingRuleOfferRequest'

    SearchResult:
      type: object
      properties:
        id:
          type: string
        label:
          type: string
        slug:
          type: string
        active:
          type: boolean
        role:
          type: string
    SearchResultListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/SearchResult'
    SearchUser:
      type: object
      properties:
        id:
          type: string
          format: uuid
        label:
          type: string
        role:
          $ref: '#/components/schemas/Role'
        email:
          type: string
    SearchUserListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/SearchUser'

    AdminDashboardSummary:
      type: object
      additionalProperties: false
      required: [clicks_today, conversions_today, pending_approval, active_partners, revenue_today, payout_today, profit_today, roi_today]
      properties:
        clicks_today:
          type: integer
        conversions_today:
          type: integer
        pending_approval:
          type: integer
        active_partners:
          type: integer
        revenue_today:
          type: number
        payout_today:
          type: number
        profit_today:
          type: number
        roi_today:
          type: number
    MerchantDashboardSummary:
      type: object
      additionalProperties: false
      required: [publishers_count, conversions_today, earnings_today]
      properties:
        publishers_count:
          type: integer
        conversions_today:
          type: integer
        earnings_today:
          type: number
    PublisherDashboardSummary:
      type: object
      additionalProperties: false
      required: [clicks_today, conversions_today, earnings_today]
      properties:
        clicks_today:
          type: integer
        conversions_today:
          type: integer
        earnings_today:
          type: number
    DashboardSummaryResponse:
      type: object
      required: [data]
      properties:
        data:
          description: Role-specific summary selected from the authenticated API key role.
          oneOf:
            - $ref: '#/components/schemas/AdminDashboardSummary'
            - $ref: '#/components/schemas/MerchantDashboardSummary'
            - $ref: '#/components/schemas/PublisherDashboardSummary'
    DashboardHourlyBucket:
      type: object
      additionalProperties: false
      required: [hour, clicks, conversions, payout]
      properties:
        hour:
          type: integer
          minimum: 0
          maximum: 23
        clicks:
          type: integer
        conversions:
          type: integer
        payout:
          type: number
    HourlyStatsResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/DashboardHourlyBucket'

    PostbackLog:
      type: object
      additionalProperties: false
      required: [id, company_id, click_id, offer_id, publisher_id, transaction_id, event_type, ip_address, raw_query, http_status, response_msg, outcome, postback_event_id, created_at]
      properties:
        id:
          type: integer
          format: int64
        company_id:
          type: [string, 'null']
          format: uuid
        click_id:
          type: [string, 'null']
        offer_id:
          type: [string, 'null']
          format: uuid
        publisher_id:
          type: [string, 'null']
          format: uuid
        transaction_id:
          type: [string, 'null']
        event_type:
          type: [string, 'null']
        ip_address:
          type: [string, 'null']
        raw_query:
          type: [string, 'null']
        http_status:
          type: integer
        response_msg:
          type: string
        outcome:
          type: string
        postback_event_id:
          type: [string, 'null']
          format: uuid
        created_at:
          type: string
          format: date-time
    PostbackLogListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/PostbackLog'
        meta:
          $ref: '#/components/schemas/Meta'
    AuditLog:
      type: object
      additionalProperties: false
      required: [id, company_id, user_id, user_name, action, entity_type, entity_id, details, ip, created_at]
      properties:
        id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        user_name:
          type: string
        action:
          type: string
        entity_type:
          type: string
        entity_id:
          type: string
        details:
          type: object
          additionalProperties: true
        ip:
          type: string
        created_at:
          type: string
          format: date-time
    AuditLogListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/AuditLog'
        meta:
          $ref: '#/components/schemas/Meta'
    LoginHistory:
      type: object
      additionalProperties: false
      required: [id, company_id, user_id, user_name, user_email, ip, user_agent, ip_fraud_score, ip_is_proxy, ip_proxy_type, ip_country_code, ip_city, ip_asn, ip_isp, created_at]
      properties:
        id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        user_name:
          type: string
        user_email:
          type: string
          format: email
        ip:
          type: string
        user_agent:
          type: string
        ip_fraud_score:
          type: [integer, 'null']
        ip_is_proxy:
          type: [boolean, 'null']
        ip_proxy_type:
          type: string
        ip_country_code:
          type: string
        ip_city:
          type: string
        ip_asn:
          type: string
        ip_isp:
          type: string
        created_at:
          type: string
          format: date-time
    LoginHistoryListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/LoginHistory'
        meta:
          $ref: '#/components/schemas/Meta'
