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:
- Submit this request and save
data.competitive_report_idfrom the202response. - Call Check report status with the same
store_idand the saved report id. - Continue polling while
data.statusisqueued,pending, orprocessing. - Read
data.report_dataafter the report completes, or inspectdata.failure_reasonwhendata.statusisfailed.
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
| Environment | URL |
|---|---|
| Production | https://production-api.shoutaboutus.com |
| Development | https://development-api.shoutaboutus.com |
Endpoint
POST /api/v1/account/competitive-report
Authentication
Partner or Account token
store_idmust 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
| Field | Type | Required | Description |
|---|---|---|---|
store_id | integer | Yes | The location to compare. Must exist and be inside your account tree. |
review_site_id | integer | Yes | The review site to compare on. Must exist and be linked to the location, and the location needs its review-site URL for that site. |
year | integer | Yes | The report year, 2000 to 2100. |
month | integer | No | The report month, 1 to 12. Leave it out for a full-year report. |
theme | string | No | A 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. |
competitors | array | Yes | One or two competitors. Your plan may allow only one. |
competitors[].name | string | Yes | The competitor's name. At most 255 characters. |
competitors[].profile_key | string (URL) | Yes | At 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[].id | string | No | Your id for the competitor. At most 255 characters. Leave it out to have one made from profile_key. |
competitors[].phone | string | No | At most 64 characters. |
competitors[].description | string | No | At most 2,048 characters. |
competitors[].tags | array of strings | No | At most 30 tags, each at most 64 characters. |
competitors[].address | object | No | street (at most 255 characters), city, state (at most 128 each), zip (at most 32) and country (at most 64), all optional strings. |
force | boolean | No | true 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.
| Field | Type | Description |
|---|---|---|
competitive_report_id | integer | The report's id. Use it as report_id in Check report status. |
store_id | integer | The location. |
review_site_id | integer | The review site. |
publisher | string | The review site's source, e.g. maps.google.com. |
year | integer | The report year. |
month | integer or null | The report month, or null for a full-year report. |
theme | string | The report theme. |
status | string | The report's state. A new report is usually queued. |
code | integer or null | The report generator's result code. |
reason_code | string or null | The report generator's reason code, set when a report fails. |
task_id | string or null | The report generator's task id. |
competitors | array of objects | The 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_count | integer | How many competitors. |
review_count | integer or null | How many reviews the report scored. null until it's complete. |
report_data | object or null | The report itself. null until it's complete. |
failure_reason | string or null | Why the report failed, when it did. |
failure_details | string or null | More detail on the failure, when there is any. |
created_at | string or null | When the report was requested (ISO 8601). |
completed_at | string or null | When the report finished (ISO 8601). |
remaining | integer or null | Only on 202. Competitive reports left on the location's allowance this period; null when there's no cap. |
overage | boolean | Only on 202. true when this report was billed as overage. |
usage_log_id | integer | Only on 202. The id of the usage record for this report. |
deduped | boolean | Only 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."
}| Status | Meaning |
|---|---|
200 | A matching report already exists; it's returned and nothing is billed. |
202 | A new report is queued. Poll Check report status. |
401 | The token is missing, expired or invalid. |
403 | The 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. |
404 | The location's partner couldn't be found. |
422 | A 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. |
429 | The report provider is rate-limiting requests. Try again later. |
500 | Unexpected server error. |
502 | The report provider rejected the request (rd_error_code says why) or returned an error. |
504 | The 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}'Updated 17 days ago
