---
title: "Start a paid scan"
method: POST
path: "/api/v1/runs"
tags: ["runs"]
---

# Start a paid scan

`POST /api/v1/runs`

## Request

- Base URL: `https://index365.co`
- URL: `https://index365.co/api/v1/runs`
- Auth: HTTP bearer

Starts a paid Marketing Signal scan and returns 202 with the run immediately. Pass projectId for an existing project, or omit it with an absolute URL to reuse or create that URL's canonical-domain project in the key's organization. scanMode defaults to paid_marketing_signal_v2, the only startable mode; legacy scanModes return 400 error.code=legacy_product_not_creatable (their completed runs stay readable). Spends one Page Check per run; requires the runs:write scope and an active paid plan. Poll get_run for completion. Send an Idempotency-Key header so retries never double-spend. Single-flight is per product per project.

## Headers

- `Idempotency-Key` string

## Request body

- union
  - object
    - `projectId` string, uuid, required: Existing project. Omit with an absolute URL to auto-create or reuse that domain project.
    - `scanMode` 'paid_marketing_signal_v2'
    - `url` string: The single page this run audits. With projectId, use an absolute same-domain URL or root-relative path like '/pricing'; omit it for the project homepage. Without projectId, use an absolute public-domain URL and its normalized domain project is created or reused.
    - `idempotencyKey` string
    - `topicBrief` object: Optional approved targeting brief. It is normalized and capped at 12 unique terms across roles; absent means target alignment is not evaluated.
      - `version` 1, required
      - `primary` string, required
      - `secondary` string[]
      - `supporting` string[]
  - object
    - `projectId` string, uuid: Existing project. Omit with an absolute URL to auto-create or reuse that domain project.
    - `scanMode` 'paid_marketing_signal_v2'
    - `url` string, required: The single page this run audits. With projectId, use an absolute same-domain URL or root-relative path like '/pricing'; omit it for the project homepage. Without projectId, use an absolute public-domain URL and its normalized domain project is created or reused.
    - `idempotencyKey` string
    - `topicBrief` object: Optional approved targeting brief. It is normalized and capped at 12 unique terms across roles; absent means target alignment is not evaluated.
      - `version` 1, required
      - `primary` string, required
      - `secondary` string[]
      - `supporting` string[]

## Response `200`

Idempotent replay of an existing run (idempotentReplay=true).

- object: Run status + summary. score/severityCounts are null until status=completed. score is also null when scoreStatus=unverified because source-only evidence cannot support a numeric claim.
  - `schemaVersion` integer
  - `product` 'ai_readiness' | 'marketing_signal' | 'marketing_signal_v2' | 'website_security'
  - `runId` string, uuid
  - `projectId` string, uuid, nullable
  - `status` 'pending' | 'running' | 'completed' | 'failed' | 'failed_auto_credit' | 'refunded'
  - `progressPct` integer
  - `currentStep` string, nullable
  - `url` string
  - `scanMode` string, nullable
  - `model` string, nullable
  - `costUsd` number
  - `error` string, nullable
  - `createdAt` string, date-time
  - `startedAt` string, date-time, nullable
  - `completedAt` string, date-time, nullable
  - `score` integer, nullable
  - `scoreStatus` 'unverified' | 'null', nullable: unverified when source-only page evidence needs rendered verification.
  - `band` 'Weak' | 'Fair' | 'Good' | 'Excellent' | 'null', nullable: Marketing Signal runs only: the band for `score`.
  - `coverageLine` string: Marketing Signal runs only: what the run graded, as a sentence.
  - `restated` boolean: Marketing Signal runs only: true when this run stored a different headline, which happens on runs scored before the corrected weighting.
  - `restatementNote` string
  - `legacyStoredScore` integer, nullable: Marketing Signal runs only: the headline this run stored. For reconciliation, never for display.
  - `severityCounts` object, nullable
  - `findingsTotal` integer, nullable
  - `coverage` object, nullable: Present for a local on-page scan (CLI `scan local`): { mode: 'local_on_page', notEvaluated: [...] } lists the checks not run (DNS/TLS/redirects/foundation files/email auth/response-header security). Null for a normal full scan.
    - `mode` 'local_on_page'
    - `notEvaluated` string[]
  - `humanUrl` string
  - `links` object
    - `self` string
    - `findings` string
    - `report` string

## Other responses

- `202`: Run accepted and queued.
- `400`: invalid_json | validation_failed | legacy_product_not_creatable | url_not_same_domain | auto_project_requires_public_domain.
- `401`: Missing/unknown/revoked API key.
- `402`: no_active_subscription | insufficient_credits.
- `403`: insufficient_scope | key_project_mismatch | project_scoped_key.
- `404`: Resource not found in this organization.
- `409`: run_in_flight: an audit is already running for this project; project_limit_reached when a future project cap is reached.
- `429`: Rate limit exceeded (60 req/min per key). Honor Retry-After.

---

[API](https://skmtc.dev/index365/apis/index365-api.md) · [All operations](https://skmtc.dev/index365/apis/index365-api/llms.txt) · [OpenAPI document](https://skmtc.dev/index365/apis/index365-api/revisions/87dd7b85edcb?raw)
