Generate a competitive report

Overview

Start a comparison of a location against one or two competitors on a site.

Use this when a business wants to see how it stacks up.

You'll need the store_id, the review_site_id, the year, and one or two competitors (each a name and a profile URL on that review site). theme and month are optional.

Returns a report id to poll. Billed on submit; counts against your allowance. If a report for the same location, review site, period, theme and competitors already exists (and hasn't failed), that report is returned instead (200, deduped: true) and nothing is billed; send force: true to generate a new one.

To complete the workflow:

  1. Submit this request and save data.competitive_report_id from the 202 response.
  2. Call Check report status 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. See Authentication.
  • The location's id (store_id), and a review site linked to the location with its review-site URL set (review_site_id).
  • Competitive analysis must be on the location's plan.

Base URL

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

Endpoint

POST /api/v1/account/competitive-report

Authentication

Partner or Account token

  • store_id must be inside your own account tree. A Partner token can generate reports for any location under the partner; an Account token only for its own locations.
  • A location outside your tree is rejected with 422.

Rate limit

60 requests per minute, counted per user (the token’s user). See Rate limits.

Request parameters

None.

Request body

FieldTypeRequiredDescription
store_idintegerYesThe location to compare. Must exist and be inside your account tree.
review_site_idintegerYesThe review site to compare on. Must exist and be linked to the location, and the location needs its review-site URL for that site.
yearintegerYesThe report year, 2000 to 2100.
monthintegerNoThe report month, 1 to 12. Leave it out for a full-year report.
themestringNoA theme slug from List comparison themes, e.g. home-services. At most 64 characters. Leave it out to use the account's industry (with the system default as the final fallback); general is no longer accepted by the reporting engine.
competitorsarrayYesOne or two competitors. Your plan may allow only one.
competitors[].namestringYesThe competitor's name. At most 255 characters.
competitors[].profile_keystring (URL)YesAt most 1,024 characters. The competitor's own business profile (review) page on the same review site as review_site_id, in the same form as your location's own review-site URL for that site. Only the URL format is checked here; the report provider then checks the competitor and can reject the request with 502 (rd_error_code: VALIDATION_ERROR). Confirm the accepted URL form for each review site with your account manager. Each competitor needs a different URL.
competitors[].idstringNoYour id for the competitor. At most 255 characters. Leave it out to have one made from profile_key.
competitors[].phonestringNoAt most 64 characters.
competitors[].descriptionstringNoAt most 2,048 characters.
competitors[].tagsarray of stringsNoAt most 30 tags, each at most 64 characters.
competitors[].addressobjectNostreet (at most 255 characters), city, state (at most 128 each), zip (at most 32) and country (at most 64), all optional strings.
forcebooleanNotrue generates a new report even when one already exists for the same location, review site, period, theme and competitors; it's billed again. Defaults to false.

Request payload

{
  "store_id": 3007,
  "review_site_id": 44,
  "year": 2026,
  "month": 9,
  "theme": "home-services",
  "competitors": [
    {
      "name": "Pacific Breeze Heating & Air",
      "profile_key": "<competitor-profile-url>",
      "phone": "+15555550100",
      "address": {
        "street": "123 Main St",
        "city": "San Diego",
        "state": "CA",
        "zip": "92101",
        "country": "US"
      }
    }
  ],
  "force": false
}

Response body

The payload is inside data. Status 202 when a new report is queued; 200 when an existing report is returned.

FieldTypeDescription
competitive_report_idintegerThe report's id. Use it as report_id in Check report status.
store_idintegerThe location.
review_site_idintegerThe review site.
publisherstringThe review site's source, e.g. maps.google.com.
yearintegerThe report year.
monthinteger or nullThe report month, or null for a full-year report.
themestringThe report theme.
statusstringThe report's state. A new report is usually queued.
codeinteger or nullThe report generator's result code.
reason_codestring or nullThe report generator's reason code, set when a report fails.
task_idstring or nullThe report generator's task id.
competitorsarray of objectsThe competitors as sent to the report generator: id, name, profile_key, and any street, city, state, zip, country, phone, description and tags you sent.
competitor_countintegerHow many competitors.
review_countinteger or nullHow many reviews the report scored. null until it's complete.
report_dataobject or nullThe report itself. null until it's complete.
failure_reasonstring or nullWhy the report failed, when it did.
failure_detailsstring or nullMore detail on the failure, when there is any.
created_atstring or nullWhen the report was requested (ISO 8601).
completed_atstring or nullWhen the report finished (ISO 8601).
remaininginteger or nullOnly on 202. Competitive reports left on the location's allowance this period; null when there's no cap.
overagebooleanOnly on 202. true when this report was billed as overage.
usage_log_idintegerOnly on 202. The id of the usage record for this report.
dedupedbooleanOnly on 200. true: an existing report was returned and nothing was billed.

The response also carries the allowance in headers: X-Feature (competitive_analysis), X-Feature-Limit, X-Feature-Used, X-Feature-Remaining (unlimited when there's no cap) and X-Feature-Hard-Stop (1 or 0).

Response payload

{
  "data": {
    "competitive_report_id": 54,
    "store_id": 3007,
    "review_site_id": 44,
    "publisher": "maps.google.com",
    "year": 2026,
    "month": 9,
    "theme": "home-services",
    "status": "queued",
    "code": null,
    "reason_code": null,
    "task_id": "605eefee-6253-4c66-a831-5945e17dce64",
    "competitors": [
      {
        "id": "comp_4b6b2b957d4856aa",
        "name": "Pacific Breeze Heating & Air",
        "profile_key": "<competitor-profile-url>",
        "street": "123 Main St",
        "city": "San Diego",
        "state": "CA",
        "zip": "92101",
        "country": "US",
        "phone": "+15555550100"
      }
    ],
    "competitor_count": 1,
    "review_count": null,
    "report_data": null,
    "failure_reason": null,
    "failure_details": null,
    "created_at": "2026-10-01T09:00:00+00:00",
    "completed_at": null,
    "remaining": 4,
    "overage": false,
    "usage_log_id": 5124
  }
}

Examples

Success payload

A new report queued (202): see Response payload.

A matching report already exists (200):

{
  "data": {
    "competitive_report_id": 54,
    "store_id": 3007,
    "review_site_id": 44,
    "publisher": "maps.google.com",
    "year": 2026,
    "month": 9,
    "theme": "home-services",
    "status": "queued",
    "code": null,
    "reason_code": null,
    "task_id": "605eefee-6253-4c66-a831-5945e17dce64",
    "competitors": [
      {
        "id": "comp_4b6b2b957d4856aa",
        "name": "Pacific Breeze Heating & Air",
        "profile_key": "<competitor-profile-url>",
        "street": "123 Main St",
        "city": "San Diego",
        "state": "CA",
        "zip": "92101",
        "country": "US",
        "phone": "+15555550100"
      }
    ],
    "competitor_count": 1,
    "review_count": null,
    "report_data": null,
    "failure_reason": null,
    "failure_details": null,
    "created_at": "2026-10-01T09:00:00+00:00",
    "completed_at": null,
    "deduped": true
  }
}

Error payload

403 Competitive report allowance used up

{
  "message": "Limit reached for 'Competitive Analysis': 2 of 2 units consumed this period.",
  "status": "error",
  "errors": {
    "remaining": 0
  }
}

422 Competitors missing

{
  "message": "The competitors field is required.",
  "errors": {
    "competitors": [
      "The competitors field is required."
    ]
  }
}

422 Too many competitors

{
  "message": "The competitors field must not have more than 2 items.",
  "errors": {
    "competitors": [
      "The competitors field must not have more than 2 items."
    ]
  }
}

422 profile_key isn't a URL

{
  "message": "The competitors.0.profile_key field must be a valid URL.",
  "errors": {
    "competitors.0.profile_key": [
      "The competitors.0.profile_key field must be a valid URL."
    ]
  }
}

422 No review-site URL for the location

{
  "message": "This store has no review-site URL for the selected publisher — required for a competitive report.",
  "status": "error",
  "errors": {
    "store_id": 3007,
    "review_site_id": 44
  }
}

502 Report provider rejected the request

{
  "message": "Competitive report provider rejected the request.",
  "status": "error",
  "errors": {
    "reference": {
      "rd_request_id": "e56dcffeee964e87858fe338da3c9c19",
      "rd_error_code": "VALIDATION_ERROR"
    }
  }
}

429 Too many requests

{
  "message": "Too Many Attempts."
}
StatusMeaning
200A matching report already exists; it's returned and nothing is billed.
202A new report is queued. Poll Check report status.
401The token is missing, expired or invalid.
403The competitive report allowance is used up on a hard-stop plan, competitive analysis isn't on the location's plan, or the location has no active plan.
404The location's partner couldn't be found.
422A field is missing or invalid; the location is outside your tree; review_site_id isn't linked to the location, or the location has no review-site URL for that review site (This store has no review-site URL for the selected publisher — required for a competitive report.); two competitors share a URL; there are more competitors than your plan allows; or the location isn't set up with the report provider yet.
429The report provider is rate-limiting requests. Try again later.
500Unexpected server error.
502The report provider rejected the request (rd_error_code says why) or returned an error.
504The report provider is unreachable or timed out.

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": 3007, "review_site_id": 44, "year": 2026, "month": 9, "theme": "home-services", "competitors": [{"name": "Pacific Breeze Heating & Air", "profile_key": "<competitor-profile-url>", "phone": "+15555550100", "address": {"street": "123 Main St", "city": "San Diego", "state": "CA", "zip": "92101", "country": "US"}}], "force": false}'

Did this page help you?