---
title: "Start a run from a local repository surface"
method: POST
path: "/api/v1/runs/analyze-repo"
tags: ["runs"]
---

# Start a run from a local repository surface

`POST /api/v1/runs/analyze-repo`

## Request

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

Grade a Marketing Surface Artifact produced by `index365 scan repo` on the caller's machine. The artifact carries CITATIONS (vendor, file, line) and counts, never source bodies, `.env` values, or absolute paths. This server never reads a filesystem and never fetches anything, including URLs referenced inside the artifact.

A repository run reports `evidenceMode: local_source` and carries `local_repository` provenance. It does NOT produce a headline Marketing Signal score: source cannot prove response headers, DNS, TLS, redirects, email authentication, or anything requiring a rendered page, so those checks are absent rather than guessed. Escalate to a live scan through POST /api/v1/runs to grade the published experience.

`projectId` is required, because a repository scan carries no URL to resolve a project from. Re-uploading an unchanged tree replays the previous run rather than spending again.

## Request body

- object
  - `projectId` string, uuid, required
  - `idempotencyKey` string
  - `surface` object, required: Marketing Surface Artifact. Produce it with `index365 scan repo <dir> --dry-run`, which prints the exact bytes this endpoint receives.

## Response `202`

Run accepted.

- 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

- `400`: invalid_json, validation_failed, project_required, or an invalid surface.
- `401`: Missing/unknown/revoked API key.
- `403`: Key lacks the required scope (error.code=insufficient_scope).
- `404`: Project not found.
- `409`: project_archived.
- `413`: surface_too_large.
- `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)
