Submit competitive report (generate)

Overview

Queue an asynchronous ReviewData competitive report. It compares one store location with one or two competitors on a shared publisher (review site). The request consumes one unit from the store's competitive_analysis plan quota at submission. Hard-stop plans return 403 when exhausted, while soft-stop plans bill the call as overage. An identical non-failed report is reused unless you set force. The response returns immediately with status: queued and a task_id. The completed report arrives through the COMPETITIVE_REPORT_RESULT webhook.

Complete the workflow

  1. Submit this request and save data.competitive_report_id from the 202 response.
  2. Call Check status (poll until complete) with the same store_id and the saved report ID.
  3. Continue polling while data.status is queued, pending, or processing.
  4. Read data.report_data after the report completes, or inspect data.failure_reason when data.status is failed.

Prerequisites

  • A bearer token. Callable with Partner and Account tokens.

Base URL

EnvironmentURL
Productionhttps://production-api.shoutaboutus.com
Developmenthttps://development-api.shoutaboutus.com

Endpoint

POST /api/v1/account/competitive-report

Authentication

  • Requires a bearer token in the Authorization: Bearer <bearer-token> header.

  • Who can call it: Partner and Account tokens.

  • The route lives directly under the authenticated group (not behind partner-only access), so any authenticated user (Partner or Account) may call it.

  • The target store must sit inside your own account tree, keyed on store_id, which walks the auth user's company hierarchy.

  • As a result, a Partner reaches any store in its tree, an Account only its own.

  • review_site_id must additionally be linked to the store (checked during validation).

Rate limit

  • No rate limit.

Request body

  • Body:
FieldTypeRequiredDescription
store_idintegeryesMust be an existing location inside your own account tree (must be in caller's company hierarchy).
review_site_idintegeryesMust be an existing review site and must also be linked to the store.
yearintegeryesbetween:2000,2100.
monthintegernobetween:1,12; omit for a full-year report.
themestringnomax:64. Optional — when omitted it is auto-resolved from the account's industry (system default as final fallback). A theme slug from List available themes (e.g. home-services); general is no longer accepted by the reporting engine.
competitorsarrayyes1-2 items.
competitors.*.namestringyesmax:255.
competitors.*.profile_keystring (url)yesmax:1024. Competitor's review-page URL on the publisher.
competitors.*.idstringnomax:255.
competitors.*.phonestringnomax:64.
competitors.*.descriptionstringnomax:2048.
competitors.*.tagsarraynomax:30, each tag string max:64.
competitors.*.addressobjectnooptional street/city/state/zip/country strings.
forcebooleannoExplicit recall — bypasses dedup and bills a fresh RD run.
{
  "store_id": 42,
  "review_site_id": 7,
  "year": 2026,
  "month": 5,
  "theme": "home-services",
  "competitors": [
    {
      "name": "Pacific Breeze Heating & Air",
      "profile_key": "https://www.google.com/maps/place/pacific-breeze",
      "phone": "+15551234567",
      "address": {
        "street": "123 Main St",
        "city": "San Diego",
        "state": "CA",
        "zip": "92101",
        "country": "US"
      }
    }
  ],
  "force": false
}

Response

  • data.usage_log_id (integer) — usage record id on a fresh submit.

  • data.deduped (boolean) — true when a 200 returns an existing (deduped) report instead of creating a new one.

  • data.competitive_report_id (integer) — the report ID. Use it as report_id when you call Check status (poll until complete).

  • data.store_id (integer)

  • data.review_site_id (integer|null)

  • data.publisher (string) — resolved publisher slug.

  • data.year (integer)

  • data.month (integer|null)

  • data.theme (string)

  • data.status (string) — typically queued (or pending/existing status on dedup).

  • data.task_id (string|null) — RD task id.

  • data.competitors (array) — normalised competitor blocks.

  • data.competitor_count (integer)

  • data.review_count (integer|null)

  • data.report_data (object|null) — null until the webhook lands.

  • data.failure_reason (string|null)

  • data.failure_details (mixed|null)

  • data.completed_at (string|null, ISO 8601)

  • data.remaining (integer|null) — quota left after this submit (merged via extra).

  • data.overage (boolean) — whether this call was billed as overage.

202 Queued · 202

{
  "data": {
    "competitive_report_id": 1001,
    "store_id": 42,
    "review_site_id": 7,
    "publisher": "google",
    "year": 2026,
    "month": 5,
    "theme": "home-services",
    "status": "queued",
    "task_id": "9a893013-9aa8-4b8f-b3ff-6fd72f00a6ce",
    "competitor_count": 1,
    "remaining": 4,
    "overage": false
  }
}

403 Quota exceeded / feature disabled · 403

{
  "status": "error",
  "message": "Competitive analysis quota exceeded for this store.",
  "errors": []
}

422 Validation failed · 422

{
  "message": "The given data was invalid.",
  "errors": {
    "competitors": [
      "At least one competitor is required."
    ]
  }
}

Errors

StatusMeaning
401The bearer token is missing, expired or invalid
403Quota exceeded / feature disabled
422Validation failed
500Unexpected server error

Example request

curl --request POST \
  --url "https://production-api.shoutaboutus.com/api/v1/account/competitive-report" \
  --header 'Authorization: Bearer <bearer-token>' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{"store_id": 42, "review_site_id": 7, "year": 2026, "month": 5, "theme": "home-services", "competitors": [{"name": "Pacific Breeze Heating & Air", "profile_key": "https://www.google.com/maps/place/pacific-breeze", "phone": "+15551234567", "address": {"street": "123 Main St", "city": "San Diego", "state": "CA", "zip": "92101", "country": "US"}}], "force": false}'

Did this page help you?