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_start and range_end, both Y-m-d H:i:s.
  • Optional: a store_id or account_id (or your own client_location_id / client_account_id) to narrow the report. Leave them out to cover every location under your partner.

Base URL

EnvironmentURL
Productionhttps://production-api.shoutaboutus.com
Developmenthttps://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 500 with Access denied. Partner plan required.
  • Which locations are covered: account_id (that account's locations) if sent, otherwise store_id (one location), otherwise every location under your partner; when the user behind the token is limited to some accounts, only those. client_location_id and client_account_id are used when store_id and account_id aren'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

FieldTypeRequiredDescription
range_startstringYesStart of the date range, as Y-m-d H:i:s.
range_endstringYesEnd of the date range, as Y-m-d H:i:s.
store_idintegerNoOne location to report on. Must exist and be inside your tree.
account_idintegerNoOne account under your partner (an account id, not a location). Must exist and be inside your tree.
client_location_idstringNoYour 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_idstringNoYour 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.

FieldTypeDescription
startstringrange_start, as you sent it.
endstringrange_end, as you sent it.
companyobjectHeader details for the report.
company.partner_namestringYour partner's name.
company.account_namestringThe 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_namestringThe 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.logostringURL of your white-label logo, or an empty string when none is set.
kpis_dataobjectThe headline numbers for the range and the previous period.
kpis_data.total_reviewsintegerReviews dated within the range.
kpis_data.avg_ratingnumberAverage star rating of the reviews in the range, to 2 decimals.
kpis_data.total_respondedintegerReviews in the range that have a response of any kind.
kpis_data.response_rate_pctnumbertotal_responded as a percentage of total_reviews, to 2 decimals.
kpis_data.awaiting_responseintegerReviews 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_ratenumberPercentage 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_timenumber or stringAverage time to respond, in days, to 2 decimals. The string "0" when there are no responses.
kpis_data.noLoginNoResponseintegerReviews whose response is waiting because no login is saved for the site.
kpis_data.noLoginNoResponsintegerMisspelt alias of noLoginNoResponse, kept for existing clients. Use noLoginNoResponse.
kpis_data.total_reviews_previousintegerReviews in the previous period: a window of the same length immediately before range_start.
kpis_data.avg_rating_previousnumberAverage star rating in the previous period, to 2 decimals.
kpis_data.response_previousintegerResponses to the reviews in the previous period.
reviewsSentimentsobjectHow the reviews in the range split by star rating.
reviewsSentiments.negativeReviewsintegerReviews rated below 3 stars. For Facebook, a review with no star rating (0) is not counted as negative.
reviewsSentiments.negativeReviews_percentnumberNegative reviews as a percentage of all reviews in the range, to 2 decimals. 0 when there are none.
reviewsSentiments.positiveReviewsintegerReviews rated above 3 stars.
reviewsSentiments.positiveReviews_percentnumberPositive reviews as a percentage of all reviews in the range, to 2 decimals.
reviewsSentiments.neutralReviewsintegerAll other reviews: rated 3, or with no star rating.
reviewsSentiments.neutralReviews_percentnumberNeutral reviews as a percentage of all reviews in the range, to 2 decimals.
review_response_by_sitearray of objectsOne entry per review site with reviews in the range, most reviews first.
review_response_by_site[].idintegerThe review site's id (review_site_id).
review_response_by_site[].review_sitestringThe review site's name in lower case, e.g. google.
review_response_by_site[].review_site_logostring or nullAlways null in this report.
review_response_by_site[].total_reviewsintegerReviews from this site dated within the range.
review_response_by_site[].avg_ratingnumberAverage star rating of this site's rated reviews in the range, to 2 decimals.
review_response_by_site[].per_of_totalnumberThis site's share of all reviews in the range, as a percentage to 2 decimals.
review_response_by_site[].total_responseintegerResponses to this site's reviews in the range. Failed responses aren't counted.
review_response_by_site[].per_total_responsenumbertotal_response as a percentage of total_reviews, to 2 decimals.
review_response_by_locationarray of objectsOne entry per location covered.
review_response_by_location[].idintegerThe location's id (store_id).
review_response_by_location[].namestringThe 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_reviewsintegerReviews dated within the range.
review_response_by_location[].avg_ratingnumberAverage star rating of the reviews in the range, to 2 decimals.
review_response_by_location[].per_of_totalnumberThis location's share of all reviews in the range, as a percentage to 2 decimals.
review_response_by_location[].total_responseintegerResponses submitted to the location's reviews in the range.
review_response_by_location[].per_total_responsenumberPercentage of the location's reviews in the range with a response that didn't fail, to 2 decimals.
review_response_by_location[].avg_response_timenumber or stringAverage time to respond, in days, to 2 decimals. The string "0" when there are no responses.
response_feedarray of objectsEvery review in the range, newest first, with its response.
response_feed[].idintegerThe review's id.
response_feed[].source_platformstringThe site the review came from, e.g. google.
response_feed[].reviewobjectThe review.
response_feed[].review.idintegerThe review's id.
response_feed[].review.reviewerstringThe reviewer's name as shown on the site.
response_feed[].review.ratingintegerStar rating.
response_feed[].review.messagestringThe review text. Empty when the review has no text.
response_feed[].review.review_datestring or nullWhen the review was posted (ISO 8601), or null when unknown.
response_feed[].reviewsiteobjectThe review site.
response_feed[].reviewsite.idintegerThe review site's id.
response_feed[].reviewsite.namestringThe review site's name, e.g. Google.
response_feed[].reviewsite.linkstringThe review site's home page.
response_feed[].reviewsite.review_site_logostringURL of the site's logo image.
response_feed[].reviewsite.review_site_colorstring or nullThe site's brand colour as a hex code, or null.
response_feed[].reviewsite.auth_typestring or nullHow the site connects (oauth, cookie, credential, restapi, default), or null when none is set.
response_feed[].reviewsite.scraping_enabledbooleanWhether we collect reviews from this site.
response_feed[].reviewsite.posting_enabledbooleanWhether we can post responses on this site.
response_feed[].storeobjectThe location the review is for.
response_feed[].store.idintegerThe location's id (store_id).
response_feed[].store.namestringThe location's name, followed by - and its client_location_id (or storeid).
response_feed[].store.slugstringThe location's slug.
response_feed[].store.addressstringThe location's address: street, city and state code, separated by commas.
response_feed[].store_reviewsiteobjectThe location's connection to the site.
response_feed[].store_reviewsite.idinteger or nullThe id of the location's connection to this site, or null when there's none.
response_feed[].store_reviewsite.urlstring or nullThe location's listing URL on the site, or null.
response_feed[].store_reviewsite.landing_page_urlstring or nullThe URL customers are sent to for leaving a review, or null.
response_feed[].store_reviewsite.connection_statusstring or nullThe connection status as stored, e.g. Connected or Disconnected, or null.
response_feed[].store_reviewsite.is_connectedbooleanWhether the location is connected to this site.
response_feed[].flagarray of objectsStatus badges for the response and any flag on the review. Empty when there are none.
response_feed[].flag[].flagstringShort status label, e.g. complete or pending.
response_feed[].flag[].titlestringA sentence explaining the status.
response_feed[].flag[].statusColorstringsuccess, warning or error.
response_feed[].flag[].iconstringIcon name for the status.
response_feed[].flag[].linksarray of objectsOnly on some error statuses: links that help fix the problem, each with name and url.
response_feed[].planstringName of the plan the latest response was sent under, or an empty string.
response_feed[].review_flagobject or nullThe 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_flaggablebooleanWhether the review can be flagged for removal.
response_feed[].flag_statusstringThe flag's status: pending, denied, approved_removed, submitted_to_platform, or none when it isn't flagged.
response_feed[].flag_detailsobject or nullDetails of the flag, or null when the review isn't flagged.
response_feed[].flag_details.reasonstringWhy the review was flagged.
response_feed[].flag_details.reason_identifierstring or nullThe flag reason's identifier, or null.
response_feed[].flag_details.flag_reason_detailstring or nullExtra detail on the reason, or null.
response_feed[].flag_details.flag_datestring or nullWhen the review was flagged (ISO 8601), or null.
response_feed[].response_capabilitiesobjectWhether a response can be sent for this review.
response_feed[].response_capabilities.manualbooleanWhether a response can be written and submitted for this review.
response_feed[].response_capabilities.aibooleanWhether an AI response can be requested for this review.
response_feed[].response_capabilities.reasonstring or nullWhy responding isn't available, e.g. past_response_unavailable, past_response_quota_reached or past_response_window_expired. null when it is.
response_feed[].responseobject or nullThe response to the review, or null when there's none.
response_feed[].response.idintegerThe response's id.
response_feed[].response.statusstringThe response's posting status, e.g. complete, pending, login_error or no_login_no_response.
response_feed[].response.public_contentsstring or nullThe public response text.
response_feed[].response.private_contentsstring or nullThe private message, where the site supports one.
response_feed[].response.response_datestring or nullWhen the response was posted, or null.
response_feed[].response.deletedobject or nullOnly 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."
}
StatusMeaning
200The report data.
401The token is missing, expired or invalid.
422A 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.
429More than 60 requests in a minute from this user.
500The 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}'

Did this page help you?