---
title: "Get the latest Marketing Signal report for a project"
method: GET
path: "/api/v1/projects/{projectId}/marketing/report"
tags: ["reports"]
---

# Get the latest Marketing Signal report for a project

`GET /api/v1/projects/{projectId}/marketing/report`

## Request

- Base URL: `https://index365.co`
- URL: `https://index365.co/api/v1/projects/{projectId}/marketing/report`
- Auth: HTTP bearer

Project-scoped convenience over get_report_context: returns the latest COMPLETED Marketing Signal run's report (six category scores, coverage line, top findings) without listing runs first. 404 error.code=no_marketing_run means no Marketing Signal run has completed yet; start one with start_audit (scanMode defaults to paid_marketing_signal_v2).

## Path parameters

- `projectId` string, uuid, required

## Query parameters

- `product` 'marketing_signal_v2'
- `scanMode` 'paid_marketing_signal_v2' | 'free_marketing_signal_v2'

## Response `200`

Marketing Signal report context.

- object: Marketing Signal report context. Six customer categories each scored 0-100, a stated coverage line, impact-ranked upside per category, and up to five evidence-backed action bundles. The score is derived from the checks the run graded, weighted by graded-check count, and is the same number the dashboard shows.
  - `schemaVersion` integer, required
  - `product` 'marketing_signal_v2', required
  - `runId` string, uuid, required
  - `projectId` string, uuid, nullable, required
  - `url` string, uri, required
  - `score` integer, nullable, required: Null when nothing on the run graded. Derived from the graded checks, weighted by graded-check count, so it always equals the roll-up of `categories`.
  - `band` 'Weak' | 'Fair' | 'Good' | 'Excellent' | 'null', nullable, required: Score band. Null exactly when score is null.
  - `scoreLabel` string, required
  - `execSummary` string, required
  - `severityCounts` object, required
    - `critical` integer, required
    - `high` integer, required
    - `medium` integer, required
    - `low` integer, required
    - `info` integer, required
  - `findingsTotal` integer, required
  - `topFindings` object[], required
    - `findingId` string, required
    - `severity` 'critical' | 'high' | 'medium' | 'low' | 'info', required
    - `category` 'discoverability' | 'structured_data' | 'metadata' | 'content_structure' | 'accessibility' | 'answerability' | 'agent_operability' | 'performance' | 'security' | 'general' | 'findability' | 'measurement' | 'conversion_path' | 'offer_clarity' | 'trust_proof' | 'schema_metadata' | 'social_preview' | 'performance_ux' | 'consent_privacy_signal' | 'connected_account', required
    - `marketingCategory` object: The report category this finding sits under. Separate from `category`, which is the shared cross-product taxonomy.
      - `key` 'seo' | 'content' | 'aeo' | 'analytics_tracking' | 'conversion' | 'trust_proof', required
      - `label` string, required
    - `title` string, required
    - `remediation` string, required
    - `affectedUrls` string[], required
  - `coverageLine` string, required: What the run actually graded. Both counts include only checks that could move a score; opportunities are excluded from both and reported separately.
  - `gradedCheckCount` integer, required
  - `applicableCheckCount` integer, required
  - `categories` object[], required: The six customer categories, in stable order. A category that graded nothing reports a null score rather than a zero.
    - `key` 'seo' | 'content' | 'aeo' | 'analytics_tracking' | 'conversion' | 'trust_proof', required
    - `label` string, required
    - `subtitle` string
    - `score` integer, nullable, required
    - `band` 'Weak' | 'Fair' | 'Good' | 'Excellent' | 'null', nullable, required
    - `passCount` integer, required
    - `failCount` integer, required
    - `evaluatedCount` integer, required: Checks that produced a pass or fail here. Also this category's weight.
    - `eligibleCount` integer, required
    - `pointsAvailable` integer, required: Headline points a perfect score in this category would still add.
  - `categoryScoreLine` string, required
  - `opportunities` object[], required: Emerging-standard upside. Never scored in either direction, so it can raise the ceiling but never damage the number.
    - `id` string, required
    - `title` string, required
    - `whyItMatters` string, required
    - `howToFix` string, required
  - `restated` boolean, required: True when the derived headline differs from the one this run stored, which happens on runs scored before the corrected weighting.
  - `restatementNote` string
  - `legacyStoredScore` integer, nullable, required: The headline this run stored, for reconciling against an older record. Never present this as the current score.
  - `metrics` object[], required: Bounded non-scoring measurements with explicit capture scope.
    - `id` string, required
    - `label` string, required
    - `value` union, required
      - string
      - number
    - `evidenceMode` 'local_source' | 'production_source' | 'rendered_when_it_ships', required
    - `scope` string, required
    - `evidence` string, required
  - `authentication` union, required
    - object
      - `label` 'Authentication', required
      - `status` 'partial', required
      - `summary` object, required: Additive, non-scoring authentication provenance for the exact audited URL. Missing lanes are disclosed, not treated as completed.
        - `capturedAt` string, date-time, required
        - `targetUrl` string, uri, required
        - `scope` string, required
        - `status` 'partial', required
        - `evidence` union[], required
          - union
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
    - object
      - `label` 'Authentication', required
      - `status` 'not_available', required
      - `summary` unknown, required
  - `topActions` object[], required
    - `id` string, required
    - `checkIds` string[], required
    - `severity` 'critical' | 'high' | 'medium' | 'low' | 'info', required
    - `title` string, required
    - `evidenceMode` 'local_source' | 'production_source' | 'rendered_when_it_ships', required
    - `applicability` object, required
      - `profile` string, required
      - `applicable` boolean, required
      - `reason` string, required
    - `exactEvidence` object[], required
      - `kind` 'text' | 'heading' | 'section' | 'list' | 'anchor' | 'cta' | 'form', required
      - `value` string, required
      - `destination` string, uri
    - `remediation` string, required
    - `severityRationale` string, required
  - `scanMode` string, nullable, required
  - `model` string, nullable, required
  - `costUsd` number, required
  - `createdAt` string, date-time, required
  - `completedAt` string, date-time, nullable, required
  - `humanUrl` string, required: Human-readable dashboard view of this exact run.

## Other responses

- `400`: invalid_product.
- `401`: Missing/unknown/revoked API key.
- `403`: Key lacks the required scope (error.code=insufficient_scope).
- `404`: not_found | no_marketing_run | results_unavailable.
- `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)
