Export performance report to PDF
Overview
Get the data for a formatted performance report for the period.
Use this for a client-ready document.
You'll need a partner token and a date range.
Returns the report data as JSON for you to lay out as a PDF; it doesn't return a file.
Prerequisites
- A Partner bearer token. See Authentication.
- A date range:
range_startandrange_end, bothY-m-d H:i:s. - Optional: a
store_idoraccount_id(or your ownclient_location_id/client_account_id) to narrow the report. Leave them out to cover every location under your partner.
Base URL
| Environment | URL |
|---|---|
| Production | https://production-api.shoutaboutus.com |
| Development | https://development-api.shoutaboutus.com |
Endpoint
POST /api/v1/partner/pdf/download/performance-report
Authentication
Partner token
- Only Partner tokens can call it. Any other token gets
500withAccess denied. Partner plan required. - Which locations are covered:
account_id(that account's locations) if sent, otherwisestore_id(one location), otherwise every location under your partner; when the user behind the token is limited to some accounts, only those.client_location_idandclient_account_idare used whenstore_idandaccount_idaren't sent. - Every id you send must be inside your own tree: a location or account under your partner. Anything else 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 |
|---|---|---|---|
range_start | string | Yes | Start of the date range, as Y-m-d H:i:s. |
range_end | string | Yes | End of the date range, as Y-m-d H:i:s. |
store_id | integer | No | One location to report on. Must exist and be inside your tree. |
account_id | integer | No | One account under your partner (an account id, not a location). Must exist and be inside your tree. |
client_location_id | string | No | Your own id for a location. Used in place of store_id when store_id isn't sent. Max 255 characters; must be inside your tree. |
client_account_id | string | No | Your own id for an account. Used in place of account_id when account_id isn't sent. Max 255 characters; must be inside your tree. |
Request payload
{
"range_start": "2026-01-01 00:00:00",
"range_end": "2026-09-30 23:59:59",
"account_id": 5994
}Response body
The payload is inside data.
| Field | Type | Description |
|---|---|---|
start | string | range_start, as you sent it. |
end | string | range_end, as you sent it. |
company | object | Header details for the report. |
company.partner_name | string | Your partner's name. |
company.account_name | string | The names of the accounts covered, separated by commas. After 3, the rest are summarised as ... and N More Brand(s). Empty when none. |
company.store_name | string | The names of the locations covered, each as name - storeid, separated by commas. After 3, the rest are summarised as ... and N More Location(s). Empty when none. |
company.logo | string | URL of your white-label logo, or an empty string when none is set. |
kpis_data | object | The headline numbers for the range and the previous period. |
kpis_data.total_reviews | integer | Reviews dated within the range. |
kpis_data.avg_rating | number | Average star rating of the reviews in the range, to 2 decimals. |
kpis_data.total_responded | integer | Reviews in the range that have a response of any kind. |
kpis_data.response_rate_pct | number | total_responded as a percentage of total_reviews, to 2 decimals. |
kpis_data.awaiting_response | integer | Reviews in the range that still have no response and that the location may respond to: reviews dated on or after the location was added, plus older reviews when the location's plan includes responding to past reviews. |
kpis_data.total_response_rate | number | Percentage of the reviews in the range with a response that didn't fail (no login saved, login error or chose not to respond aren't counted), to 2 decimals. |
kpis_data.avg_response_time | number or string | Average time to respond, in days, to 2 decimals. The string "0" when there are no responses. |
kpis_data.noLoginNoResponse | integer | Reviews whose response is waiting because no login is saved for the site. |
kpis_data.noLoginNoRespons | integer | Misspelt alias of noLoginNoResponse, kept for existing clients. Use noLoginNoResponse. |
kpis_data.total_reviews_previous | integer | Reviews in the previous period: a window of the same length immediately before range_start. |
kpis_data.avg_rating_previous | number | Average star rating in the previous period, to 2 decimals. |
kpis_data.response_previous | integer | Responses to the reviews in the previous period. |
reviewsSentiments | object | How the reviews in the range split by star rating. |
reviewsSentiments.negativeReviews | integer | Reviews rated below 3 stars. For Facebook, a review with no star rating (0) is not counted as negative. |
reviewsSentiments.negativeReviews_percent | number | Negative reviews as a percentage of all reviews in the range, to 2 decimals. 0 when there are none. |
reviewsSentiments.positiveReviews | integer | Reviews rated above 3 stars. |
reviewsSentiments.positiveReviews_percent | number | Positive reviews as a percentage of all reviews in the range, to 2 decimals. |
reviewsSentiments.neutralReviews | integer | All other reviews: rated 3, or with no star rating. |
reviewsSentiments.neutralReviews_percent | number | Neutral reviews as a percentage of all reviews in the range, to 2 decimals. |
review_response_by_site | array of objects | One entry per review site with reviews in the range, most reviews first. |
review_response_by_site[].id | integer | The review site's id (review_site_id). |
review_response_by_site[].review_site | string | The review site's name in lower case, e.g. google. |
review_response_by_site[].review_site_logo | string or null | Always null in this report. |
review_response_by_site[].total_reviews | integer | Reviews from this site dated within the range. |
review_response_by_site[].avg_rating | number | Average star rating of this site's rated reviews in the range, to 2 decimals. |
review_response_by_site[].per_of_total | number | This site's share of all reviews in the range, as a percentage to 2 decimals. |
review_response_by_site[].total_response | integer | Responses to this site's reviews in the range. Failed responses aren't counted. |
review_response_by_site[].per_total_response | number | total_response as a percentage of total_reviews, to 2 decimals. |
review_response_by_location | array of objects | One entry per location covered. |
review_response_by_location[].id | integer | The location's id (store_id). |
review_response_by_location[].name | string | The location's name, followed by - and its client_location_id (or its storeid when there's no client_location_id). |
review_response_by_location[].total_reviews | integer | Reviews dated within the range. |
review_response_by_location[].avg_rating | number | Average star rating of the reviews in the range, to 2 decimals. |
review_response_by_location[].per_of_total | number | This location's share of all reviews in the range, as a percentage to 2 decimals. |
review_response_by_location[].total_response | integer | Responses submitted to the location's reviews in the range. |
review_response_by_location[].per_total_response | number | Percentage of the location's reviews in the range with a response that didn't fail, to 2 decimals. |
review_response_by_location[].avg_response_time | number or string | Average time to respond, in days, to 2 decimals. The string "0" when there are no responses. |
response_feed | array of objects | Every review in the range, newest first, with its response. |
response_feed[].id | integer | The review's id. |
response_feed[].source_platform | string | The site the review came from, e.g. google. |
response_feed[].review | object | The review. |
response_feed[].review.id | integer | The review's id. |
response_feed[].review.reviewer | string | The reviewer's name as shown on the site. |
response_feed[].review.rating | integer | Star rating. |
response_feed[].review.message | string | The review text. Empty when the review has no text. |
response_feed[].review.review_date | string or null | When the review was posted (ISO 8601), or null when unknown. |
response_feed[].reviewsite | object | The review site. |
response_feed[].reviewsite.id | integer | The review site's id. |
response_feed[].reviewsite.name | string | The review site's name, e.g. Google. |
response_feed[].reviewsite.link | string | The review site's home page. |
response_feed[].reviewsite.review_site_logo | string | URL of the site's logo image. |
response_feed[].reviewsite.review_site_color | string or null | The site's brand colour as a hex code, or null. |
response_feed[].reviewsite.auth_type | string or null | How the site connects (oauth, cookie, credential, restapi, default), or null when none is set. |
response_feed[].reviewsite.scraping_enabled | boolean | Whether we collect reviews from this site. |
response_feed[].reviewsite.posting_enabled | boolean | Whether we can post responses on this site. |
response_feed[].store | object | The location the review is for. |
response_feed[].store.id | integer | The location's id (store_id). |
response_feed[].store.name | string | The location's name, followed by - and its client_location_id (or storeid). |
response_feed[].store.slug | string | The location's slug. |
response_feed[].store.address | string | The location's address: street, city and state code, separated by commas. |
response_feed[].store_reviewsite | object | The location's connection to the site. |
response_feed[].store_reviewsite.id | integer or null | The id of the location's connection to this site, or null when there's none. |
response_feed[].store_reviewsite.url | string or null | The location's listing URL on the site, or null. |
response_feed[].store_reviewsite.landing_page_url | string or null | The URL customers are sent to for leaving a review, or null. |
response_feed[].store_reviewsite.connection_status | string or null | The connection status as stored, e.g. Connected or Disconnected, or null. |
response_feed[].store_reviewsite.is_connected | boolean | Whether the location is connected to this site. |
response_feed[].flag | array of objects | Status badges for the response and any flag on the review. Empty when there are none. |
response_feed[].flag[].flag | string | Short status label, e.g. complete or pending. |
response_feed[].flag[].title | string | A sentence explaining the status. |
response_feed[].flag[].statusColor | string | success, warning or error. |
response_feed[].flag[].icon | string | Icon name for the status. |
response_feed[].flag[].links | array of objects | Only on some error statuses: links that help fix the problem, each with name and url. |
response_feed[].plan | string | Name of the plan the latest response was sent under, or an empty string. |
response_feed[].review_flag | object or null | The flag on the review (review_id, reason, flag_status, flag_date, flag_reason_detail, reason_identifier), or null when it isn't flagged. |
response_feed[].is_flaggable | boolean | Whether the review can be flagged for removal. |
response_feed[].flag_status | string | The flag's status: pending, denied, approved_removed, submitted_to_platform, or none when it isn't flagged. |
response_feed[].flag_details | object or null | Details of the flag, or null when the review isn't flagged. |
response_feed[].flag_details.reason | string | Why the review was flagged. |
response_feed[].flag_details.reason_identifier | string or null | The flag reason's identifier, or null. |
response_feed[].flag_details.flag_reason_detail | string or null | Extra detail on the reason, or null. |
response_feed[].flag_details.flag_date | string or null | When the review was flagged (ISO 8601), or null. |
response_feed[].response_capabilities | object | Whether a response can be sent for this review. |
response_feed[].response_capabilities.manual | boolean | Whether a response can be written and submitted for this review. |
response_feed[].response_capabilities.ai | boolean | Whether an AI response can be requested for this review. |
response_feed[].response_capabilities.reason | string or null | Why responding isn't available, e.g. past_response_unavailable, past_response_quota_reached or past_response_window_expired. null when it is. |
response_feed[].response | object or null | The response to the review, or null when there's none. |
response_feed[].response.id | integer | The response's id. |
response_feed[].response.status | string | The response's posting status, e.g. complete, pending, login_error or no_login_no_response. |
response_feed[].response.public_contents | string or null | The public response text. |
response_feed[].response.private_contents | string or null | The private message, where the site supports one. |
response_feed[].response.response_date | string or null | When the response was posted, or null. |
response_feed[].response.deleted | object or null | Only when the status is notresponding: when and how the response was removed (delete_on, status). Otherwise null. |
Response payload
{
"data": {
"start": "2026-01-01 00:00:00",
"end": "2026-09-30 23:59:59",
"company": {
"partner_name": "Acme Partner",
"account_name": "Acme Coffee",
"store_name": "Acme Coffee - Sydney, Acme Coffee - Circular Quay - Sydney",
"logo": ""
},
"kpis_data": {
"total_reviews": 223,
"avg_rating": 4.4,
"total_responded": 201,
"response_rate_pct": 90.13,
"awaiting_response": 22,
"total_response_rate": 88.34,
"avg_response_time": 1.5,
"noLoginNoResponse": 3,
"noLoginNoRespons": 3,
"total_reviews_previous": 198,
"avg_rating_previous": 4.31,
"response_previous": 170
},
"reviewsSentiments": {
"negativeReviews": 18,
"negativeReviews_percent": 8.07,
"positiveReviews": 180,
"positiveReviews_percent": 80.72,
"neutralReviews": 25,
"neutralReviews_percent": 11.21
},
"review_response_by_site": [
{
"id": 44,
"review_site": "google",
"review_site_logo": null,
"total_reviews": 223,
"avg_rating": 4.4,
"per_of_total": 100,
"total_response": 197,
"per_total_response": 88.34
}
],
"review_response_by_location": [
{
"id": 3007,
"name": "Acme Coffee - docs-test-loc-001",
"total_reviews": 223,
"avg_rating": 4.4,
"per_of_total": 100,
"total_response": 201,
"per_total_response": 88.34,
"avg_response_time": 1.5
},
{
"id": 3008,
"name": "Acme Coffee - Circular Quay - Sydney",
"total_reviews": 0,
"avg_rating": 0,
"per_of_total": 0,
"total_response": 0,
"per_total_response": 0,
"avg_response_time": "0"
}
],
"response_feed": [
{
"id": 287111,
"source_platform": "google",
"review": {
"id": 287111,
"reviewer": "Jane Smith",
"rating": 3,
"message": "",
"review_date": "2026-09-28T12:26:43.000000Z"
},
"reviewsite": {
"id": 44,
"name": "Google",
"link": "http://maps.google.com",
"review_site_logo": "https://production-api.shoutaboutus.com/static/images/img-google.png",
"review_site_color": "#4285F4",
"auth_type": null,
"scraping_enabled": false,
"posting_enabled": false
},
"store": {
"id": 3007,
"name": "Acme Coffee - docs-test-loc-001",
"slug": "sau-docs-test",
"address": "1 George St, Sydney, NSW"
},
"store_reviewsite": {
"id": 8784,
"url": "https://www.google.com/maps/place/Wynyard",
"landing_page_url": "https://www.google.com/maps/place/Wynyard",
"connection_status": "Disconnected",
"is_connected": false
},
"flag": [],
"plan": "",
"review_flag": null,
"is_flaggable": true,
"flag_status": "none",
"flag_details": null,
"response_capabilities": {
"manual": true,
"ai": true,
"reason": null
},
"response": null
}
]
}
}Examples
Success payload
See Response payload.
Error payload
422 Date range missing
{
"message": "The range start field is required. (and 1 more error)",
"errors": {
"range_start": [
"The range start field is required."
],
"range_end": [
"The range end field is required."
]
}
}422 Wrong date format
{
"message": "The range start field must match the format Y-m-d H:i:s.",
"errors": {
"range_start": [
"The range start field must match the format Y-m-d H:i:s."
]
}
}422 Account outside your tree
{
"message": "The selected account id does not belong to your company hierarchy.",
"errors": {
"account_id": [
"The selected account id does not belong to your company hierarchy."
]
}
}422 Location doesn't exist
{
"message": "The selected store id is invalid. (and 1 more error)",
"errors": {
"store_id": [
"The selected store id is invalid.",
"The selected store id does not belong to your company hierarchy."
]
}
}500 Not a Partner token
{
"message": "Internal Server Error",
"status": "error",
"errors": "Access denied. Partner plan required."
}429 Too many requests
{
"message": "Too Many Attempts."
}| Status | Meaning |
|---|---|
200 | The report data. |
401 | The token is missing, expired or invalid. |
422 | A date is missing or not Y-m-d H:i:s, or store_id, account_id, client_location_id or client_account_id doesn't exist or is outside your tree. |
429 | More than 60 requests in a minute from this user. |
500 | The token isn't a Partner token (Access denied. Partner plan required.), or an unexpected server error. |
Example request
curl --request POST \
--url "https://production-api.shoutaboutus.com/api/v1/partner/pdf/download/performance-report" \
--header 'Authorization: Bearer <bearer-token>' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{"range_start": "2026-01-01 00:00:00", "range_end": "2026-09-30 23:59:59", "account_id": 5994}'Updated 17 days ago
Did this page help you?
