openapi: 3.0.0
paths:
  /v1/ci/runs:
    post:
      description: >-
        Starts an exploration (CRAWL — full or diff) or a test execution (suite
        or single plan), on the cloud or as a local runner package. Async:
        returns immediately; poll `GET /v1/ci/runs/:id` for the result.
      operationId: triggerRun
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TriggerRunDto'
      responses:
        '200':
          description: >-
            Idempotent replay — this `idempotencyKey` was already used, so the
            original run is returned (`idempotentReplay: true`) and no new run
            starts.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CiRunResponseDto'
        '202':
          description: >-
            Run accepted and queued. Poll `pollUrl` for status. For a LOCAL run,
            `package.downloadUrl` / `package.runKey` are included.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CiRunResponseDto'
        '400':
          description: >-
            Invalid target. One of: `crawlingMode` missing for a CRAWL; not
            exactly one of `testSuiteId` / `testPlanId` for a TEST_EXECUTION;
            `platform` missing for a LOCAL run; or
            `/runs/diff-requires-full-crawl` when a DIFF crawl has no baseline
            for the environment. Body validation failures return
            `/validation/<constraint>/<field>`.
        '401':
          description: >-
            Missing, invalid, revoked, or expired organization API key
            (`/auth/missing-api-key` or `/auth/invalid-api-key`).
        '402':
          description: Organization has no available credits.
        '403':
          description: >-
            CI triggering requires a paid plan — the free plan and trials are
            not included (the response body is the bare string
            `"/billing/feature-not-available/ci-triggers"`).
        '404':
          description: >-
            Project or environment not found in this organization, or the test
            suite / test plan does not belong to the project.
        '429':
          description: >-
            Rate limit exceeded for this API key (default 60 requests/min).
            Every authenticated response carries `X-RateLimit-Limit-ci-trigger`,
            `X-RateLimit-Remaining-ci-trigger` and
            `X-RateLimit-Reset-ci-trigger` (seconds) so a pipeline can back off
            before it is throttled.
      security:
        - x-api-key: []
        - bearer: []
      summary: Trigger a run from CI/CD
      tags:
        - CI
  /v1/ci/runs/{id}:
    get:
      description: >-
        Poll until `status` is terminal (PASSED / FAILED / TIMEOUT / CANCELLED).
        Proceed with a deploy on PASSED; block otherwise. For TEST_EXECUTION
        runs the `tests` object carries the pass/fail/errored/pending counts.
      operationId: getRun
      parameters:
        - name: id
          required: true
          in: path
          description: Run ID (UUID) returned by `POST /v1/ci/runs`.
          schema:
            format: uuid
            type: string
      responses:
        '200':
          description: Current run status and, for test runs, the aggregate counts.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CiRunResponseDto'
        '401':
          description: >-
            Missing, invalid, revoked, or expired organization API key
            (`/auth/missing-api-key` or `/auth/invalid-api-key`).
        '404':
          description: No run with this id in the API key’s organization.
        '429':
          description: >-
            Rate limit exceeded for this API key (default 60 requests/min).
            Every authenticated response carries `X-RateLimit-Limit-ci-trigger`,
            `X-RateLimit-Remaining-ci-trigger` and
            `X-RateLimit-Reset-ci-trigger` (seconds) so a pipeline can back off
            before it is throttled.
      security:
        - x-api-key: []
        - bearer: []
      summary: Get run status — the CI deploy gate
      tags:
        - CI
  /v1/ci/runs/{id}/results:
    get:
      description: >-
        One entry per executed test plan: status, failure reason, per-step
        traces, and the AI interpretation. Empty for CRAWL runs.
      operationId: getRunResults
      parameters:
        - name: id
          required: true
          in: path
          description: Run ID (UUID) returned by `POST /v1/ci/runs`.
          schema:
            format: uuid
            type: string
      responses:
        '200':
          description: >-
            Array of per-test-plan results (empty array for a CRAWL run or
            before any test has finished).
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CiRunResultDto'
        '401':
          description: >-
            Missing, invalid, revoked, or expired organization API key
            (`/auth/missing-api-key` or `/auth/invalid-api-key`).
        '404':
          description: No run with this id in the API key’s organization.
        '429':
          description: >-
            Rate limit exceeded for this API key (default 60 requests/min).
            Every authenticated response carries `X-RateLimit-Limit-ci-trigger`,
            `X-RateLimit-Remaining-ci-trigger` and
            `X-RateLimit-Reset-ci-trigger` (seconds) so a pipeline can back off
            before it is throttled.
      security:
        - x-api-key: []
        - bearer: []
      summary: Per-test results for the pipeline log
      tags:
        - CI
  /v1/ci/runs/{id}/package:
    get:
      description: >-
        Streams a zip (executable + config). Only valid for runs created with
        `executionEnv: LOCAL` and a `platform`.
      operationId: downloadRunPackage
      parameters:
        - name: id
          required: true
          in: path
          description: Run ID (UUID) returned by `POST /v1/ci/runs`.
          schema:
            format: uuid
            type: string
      responses:
        '200':
          description: Zip stream (`application/zip`).
          content:
            application/zip:
              schema:
                type: string
                format: binary
        '401':
          description: >-
            Missing, invalid, revoked, or expired organization API key
            (`/auth/missing-api-key` or `/auth/invalid-api-key`).
        '403':
          description: >-
            This run is not a LOCAL run with a platform
            (`/ci/package-not-available`).
        '404':
          description: No run with this id in the API key’s organization.
        '429':
          description: >-
            Rate limit exceeded for this API key (default 60 requests/min).
            Every authenticated response carries `X-RateLimit-Limit-ci-trigger`,
            `X-RateLimit-Remaining-ci-trigger` and
            `X-RateLimit-Reset-ci-trigger` (seconds) so a pipeline can back off
            before it is throttled.
      security:
        - x-api-key: []
        - bearer: []
      summary: Download the runner package for a LOCAL run
      tags:
        - CI
info:
  title: BlackBat AI AI — CI/CD API
  version: v1
  description: >-
    Trigger BlackBat AI AI crawls and test executions from your CI/CD pipeline and
    read their results.

    Authenticate every request with an organization API key, created in the
    dashboard, sent as `Authorization: Bearer <key>` or `X-Api-Key: <key>`.
tags:
  - name: CI
    description: Start runs and read their status and results.
servers:
  - url: '{baseUrl}'
    description: Your BlackBat AI AI API host
    variables:
      baseUrl:
        default: https://api.blackbat.dev
components:
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      description: 'Organization API key, sent as `Authorization: Bearer <key>`.'
    x-api-key:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        Organization API key, sent as `X-Api-Key: <key>`. Alternative to the
        Bearer header.
  schemas:
    CiRunInterpretationDto:
      type: object
      properties:
        headline:
          type: string
          nullable: true
          description: One-line verdict on the test.
        summary:
          type: string
          nullable: true
          description: What happened, in a short paragraph.
        failureExplanation:
          type: string
          nullable: true
          description: Why the test failed, in plain words; null for a pass.
        selfHealingNote:
          type: string
          nullable: true
          description: >-
            What self-healing changed to get the test through; null when nothing
            was healed.
        suspiciousObservations:
          description: >-
            Things seen during the run that look wrong even though the test did
            not fail on them.
          type: array
          items:
            type: string
        recommendations:
          description: What to do next.
          type: array
          items:
            type: string
      required:
        - suspiciousObservations
        - recommendations
    CiRunPackageDto:
      type: object
      properties:
        downloadUrl:
          type: string
          description: >-
            Key-authenticated download for the runner package (executable +
            config). Also fetchable at GET /v1/ci/runs/:id/package.
        runKey:
          type: string
          description: >-
            Short-lived run key the downloaded runner authenticates with. Do not
            log it.
      required:
        - downloadUrl
        - runKey
    CiRunResponseDto:
      type: object
      properties:
        runId:
          type: string
          description: The run’s id; use it in `GET /v1/ci/runs/:id`.
        friendlyId:
          type: string
          nullable: true
          description: Short human-readable id shown in the dashboard, when assigned.
        type:
          type: string
          enum:
            - CRAWL
            - TEST_EXECUTION
        status:
          type: string
          enum:
            - QUEUED
            - RUNNING
            - PROCESSING
            - PASSED
            - FAILED
            - CANCELLED
            - TIMEOUT
        executionEnv:
          type: string
          enum:
            - CLOUD
            - LOCAL
          description: >-
            `CLOUD` runs on BlackBat AI’s infrastructure; `LOCAL` runs on your own
            machine from the downloaded package.
        crawlingMode:
          type: string
          enum:
            - FULL
            - DIFF
          nullable: true
          description: 'For crawls: `FULL` or `DIFF`. Null for test executions.'
        startedAt:
          type: string
          nullable: true
          format: date-time
          description: When the run started; null while it is still queued.
        endedAt:
          type: string
          nullable: true
          format: date-time
          description: When the run ended; null while it is queued or running.
        creditsSpent:
          type: number
          description: Credits the run has consumed so far.
        bugCount:
          type: number
          description: Bugs the run has found so far.
        tests:
          description: Present for TEST_EXECUTION runs.
          allOf:
            - $ref: '#/components/schemas/CiRunTestStatsDto'
        pollUrl:
          type: string
          description: Poll this for the run status.
        reportUrl:
          type: string
          description: Deep link to the run in the BlackBat AI dashboard.
        package:
          description: Present only on the create response for a LOCAL run.
          allOf:
            - $ref: '#/components/schemas/CiRunPackageDto'
        idempotentReplay:
          type: boolean
          description: >-
            True when this response replays an existing run because
            idempotencyKey was already used (HTTP 200 instead of 202).
      required:
        - runId
        - type
        - status
        - executionEnv
        - creditsSpent
        - bugCount
        - pollUrl
        - reportUrl
    CiRunResultDto:
      type: object
      properties:
        id:
          type: string
        testPlanId:
          type: string
          description: The test plan that was executed.
        status:
          type: string
          enum:
            - PASSED
            - FAILED
            - ERROR
            - PENDING
        durationMs:
          type: number
          nullable: true
          description: How long the test took, in milliseconds; null if it did not finish.
        failureReason:
          type: string
          nullable: true
          description: Top-level, human-readable reason the test did not pass.
        finalScreenshotArtifactId:
          type: string
          nullable: true
          description: Artifact id of the screenshot taken at the end of the test.
        selfHealed:
          type: boolean
          description: True if any step in this test self-healed.
        healedStepsCount:
          type: number
          description: How many steps of this test were healed.
        result:
          type: object
          additionalProperties: true
          nullable: true
          description: >-
            Full result blob (stepResults[], networkCallsObserved[],
            consoleErrors[]).
        stepTraces:
          description: >-
            Each step with its outcome, screenshot and any healing, in the order
            they ran.
          type: array
          items:
            $ref: '#/components/schemas/CiRunStepTraceDto'
        interpretation:
          description: A written explanation of the result.
          allOf:
            - $ref: '#/components/schemas/CiRunInterpretationDto'
        testPlan:
          description: The plan that was executed.
          allOf:
            - $ref: '#/components/schemas/CiRunResultTestPlanDto'
        createdAt:
          type: string
          format: date-time
      required:
        - id
        - testPlanId
        - status
        - selfHealed
        - healedStepsCount
        - stepTraces
        - interpretation
        - testPlan
        - createdAt
    CiRunResultTestPlanDto:
      type: object
      properties:
        id:
          type: string
        title:
          type: string
        description:
          type: string
          nullable: true
        steps:
          type: array
          items:
            type: object
            additionalProperties: true
          description: The ordered steps of the plan.
      required:
        - id
        - title
    CiRunStepTraceDto:
      type: object
      properties:
        stepId:
          type: string
          description: Step id within the test plan.
        status:
          type: string
          description: Outcome of this step (e.g. passed / failed).
        error:
          type: string
          nullable: true
          description: Failure detail, if any.
        screenshotArtifactId:
          type: string
          nullable: true
          description: Artifact id of the screenshot captured at this step.
        healed:
          type: boolean
          description: Whether the step was auto-healed during the run.
        originalTarget:
          type: string
          nullable: true
          description: Original selector/target before healing.
        healedTarget:
          type: string
          nullable: true
          description: Selector/target the step healed to.
        persistedToPlanAt:
          type: string
          nullable: true
          format: date-time
          description: Set when the healed target was persisted back to the plan.
      required:
        - stepId
        - status
        - healed
    CiRunTestStatsDto:
      type: object
      properties:
        total:
          type: number
          description: Test plans in the run.
        passed:
          type: number
          description: Tests that passed.
        failed:
          type: number
          description: Tests whose assertions failed.
        errored:
          type: number
          description: Tests that could not complete because of an error.
        pending:
          type: number
          description: Tests that have not finished yet.
      required:
        - total
        - passed
        - failed
        - errored
        - pending
    TriggerRunDto:
      type: object
      properties:
        maxPages:
          type: number
          description: Maximum number of pages to crawl
        maxDepth:
          type: number
          description: Maximum depth to crawl
        maxPageTemplates:
          type: number
          description: Maximum number of page templates to discover
        templateSampleSize:
          type: number
          description: >-
            Pages of a dynamic-route template (e.g. `/blog/:slug`) the crawler
            visits per template. Higher values find more per-page issues but
            make crawls longer. Crawl runs only. Default 3.
          minimum: 1
          maximum: 30
          example: 3
        maxCredits:
          type: number
          description: Maximum credits to spend on this run
        maxRunTime:
          type: number
          description: Maximum run time in minutes (<= 1440)
        crawlWorkers:
          type: number
          description: Number of parallel crawl workers (1-10)
        allowSubdomainCrawling:
          type: boolean
          description: >-
            Whether the crawler may follow links to subdomains / off-origin
            redirects
        generateDocumentation:
          type: boolean
          description: >-
            Needs a plan that includes site documentation; otherwise the request
            is refused with 403
            `/billing/feature-not-available/site-intelligence`. FULL crawls, and
            DIFF crawls whose baseline is already documented (see below): after
            the crawl passes, describe the pages and write section documentation
            and the site overview (roles, how the sections relate, journeys
            across them), as a separate billed job that runs after the crawl;
            its progress and result are shown in the dashboard. Needs
            `intelligenceMaxCredits`. When the job ends, stops at its cap or
            cannot start, the person who created the API key is notified. A DIFF
            crawl may send it only when the crawl it compares with (the
            environment’s latest crawl) has finished documentation of the whole
            site; else it is refused with
            `/validation/not-allowed/intelligence-without-current-documentation`.
            A DIFF crawl that sends nothing follows the choices of the crawl it
            compares with. Only pages and sections that changed are paid for.
            Sending any of these for a test execution run is a 400
            `/validation/not-allowed/intelligence-on-this-run`; for a DIFF crawl
            whose baseline is not documented,
            `/validation/not-allowed/intelligence-without-current-documentation`;
            `/validation/depends-on/generateUserStories` without
            generateDocumentation;
            `/validation/is-not-empty/intelligenceMaxCredits` without a limit.
        generateUserStories:
          type: boolean
          description: >-
            Also write user stories (same crawls as generateDocumentation).
            Requires generateDocumentation.
        intelligenceMaxCredits:
          type: number
          description: >-
            Spend limit, in credits, for the documentation work after the crawl.
            Required with generateDocumentation and has no default: the limit is
            your decision, the backend never fills one in. The dashboard shows a
            cost estimate when you start a documentation run by hand; use it to
            pick a value. Documentation stops at the limit and keeps what it
            finished.
          example: 5
        activeProbes:
          type: boolean
          description: >-
            CRAWL runs only. Master switch for the runner’s active probes:
            well-known file requests, replaying calls without a session, and
            login probes. Switching probes ON (this or
            `loginProbes`/`bruteForceProbe`) needs the environment’s consent:
            the environment owner must enable active security testing for this
            environment in Settings → Environments. Without consent no active
            probe ever runs and sending `true` is a 400
            `/validation/consent-required/activeProbes`. With consent the
            default is on. Passive checks (headers, cookies, content) always
            run. Sending any probe setting for a run that is not a CRAWL is a
            400 `/validation/not-allowed/probes-on-this-run`.
        loginProbes:
          type: boolean
          description: >-
            User-enumeration, session-fixation and logout checks. Log in and out
            with the environment’s real credentials, so failed attempts show in
            that account’s audit log. Needs consent and `activeProbes`; default
            on when probes are on.
        bruteForceProbe:
          type: boolean
          description: >-
            Eight quick failed logins to see whether rate limiting exists. Can
            trip an IP-based limit or WAF. Needs consent and `activeProbes`;
            always off unless set to true.
        unauthReplayMaxEndpoints:
          type: number
          description: >-
            Most GET endpoints the app already called that are replayed without
            a session (1-100). Default 30.
          minimum: 1
          maximum: 100
          example: 30
        activeProbeBudgetMs:
          type: number
          description: >-
            Wall-clock budget in ms for all active probes together
            (5000-300000). Default 45000.
          minimum: 5000
          maximum: 300000
          example: 45000
        loginInstructions:
          type: string
          description: Login instructions to apply to the environment for this run
        registrationInstructions:
          type: string
          description: Registration instructions to apply to the environment for this run
        paymentInstructions:
          type: string
          description: Payment instructions to apply to the environment for this run
        oauthAllowedIdpOrigins:
          description: >-
            OAuth/SSO identity-provider origin(s). Persisted onto the
            environment on first use, same as a manual run.
          type: array
          items:
            type: string
        proxyServer:
          type: string
          description: >-
            Network proxy to apply to the environment for this run. Persisted
            onto the environment on first use, same as a manual run.
        proxyUsername:
          type: string
          description: >-
            Username for the proxy in proxyServer, if it requires
            authentication.
        proxyPassword:
          type: string
          description: >-
            Password for the proxy in proxyServer, if it requires
            authentication.
        httpAuthUsername:
          type: string
          description: >-
            Username for the origin site's own HTTP Basic Auth challenge. Must
            be provided together with httpAuthPassword. Persisted onto the
            environment on first use, same as a manual run.
        httpAuthPassword:
          type: string
          description: >-
            Password for the origin site's own HTTP Basic Auth challenge. Must
            be provided together with httpAuthUsername.
        projectId:
          type: string
          description: Project the run targets
        environmentId:
          type: string
          description: Environment the run targets (must be in projectId)
        type:
          type: string
          enum:
            - CRAWL
            - TEST_EXECUTION
          description: CRAWL or TEST_EXECUTION
        crawlingMode:
          type: string
          enum:
            - FULL
            - DIFF
          description: Required when type = CRAWL (FULL or DIFF).
        testSuiteId:
          type: string
          description: >-
            Test suite to execute. Provide exactly one of testSuiteId /
            testPlanId when type = TEST_EXECUTION.
        testPlanId:
          type: string
          description: >-
            Single test plan to execute. Provide exactly one of testSuiteId /
            testPlanId when type = TEST_EXECUTION.
        executionEnv:
          type: string
          enum:
            - CLOUD
            - LOCAL
          default: CLOUD
          description: >-
            CLOUD runs on BlackBat AI infra (poll GET /v1/ci/runs/:id for the result);
            LOCAL returns a runner package the pipeline downloads and executes.
        platform:
          type: string
          enum:
            - MAC_SILICON
            - MAC_INTEL
            - WINDOWS
            - LINUX
          description: Required when executionEnv = LOCAL — the runner binary to hand back.
        idempotencyKey:
          type: string
          description: >-
            Client-supplied idempotency token (e.g. "<commit-sha>-<job-id>"). A
            repeated value for the same key returns the original run instead of
            starting a new one.
        externalRef:
          type: string
          description: >-
            Free-form reference surfaced on the run — branch, commit, or
            pipeline URL.
      required:
        - projectId
        - environmentId
        - type
