Ranking report

Overview

Get the account's locations ranked against each other on rating, volume, and response.

Use this for an internal leaderboard.

You'll need a token and a period.

Returns the ranked list: one ranking each by review count, rating and response rate, and an overall ranking with a grade.

Prerequisites

  • A bearer token. See Authentication.
  • A date range (range_start, range_end), both Y-m-d H:i:s.
  • Optional: a location id (store_id). Leave it out to report on every location your token can see.

Base URL

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

Endpoint

POST /api/v1/reports/ranking

Authentication

Partner or Account token

  • Partner, account and location users can call it; any other user gets 403.
  • store_id must be inside your own account tree; otherwise 422.
  • Without store_id, every location the token can see is ranked: every location under the partner for a Partner token, the account's locations for an Account token, or only the assigned locations for a user limited to some locations.

Rate limit

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

Request parameters

None.

Request body

FieldTypeRequiredDescription
range_startstringYesStart of the period, Y-m-d H:i:s (e.g. 2026-01-01 00:00:00). A date without a time is rejected.
range_endstringYesEnd of the period, Y-m-d H:i:s (e.g. 2026-09-30 23:59:59).
store_idintegerNoRank only this location. Must exist and be inside your account tree. Leave it out to rank every location your token can see.

Request payload

{
  "range_start": "2026-01-01 00:00:00",
  "range_end": "2026-09-30 23:59:59"
}

Response body

The payload is inside data.

FieldTypeDescription
account_namestring or nullName of the account: the account of store_id when sent, otherwise the caller's account. null when there's no account above (e.g. a Partner token without store_id).
intervalobjectThe period, as sent.
interval.startstringrange_start, as sent.
interval.endstringrange_end, as sent.
locationRankobjectFour rankings of the same locations. All four are [] when there are no locations.
locationRank.byReview[]array of objectsLocations ranked by review_count, best first.
locationRank.byReview[].idintegerThe location's id (store_id).
locationRank.byReview[].namestringThe location's name, then - and its store code.
locationRank.byReview[].review_countintegerReviews in the period.
locationRank.byReview[].response_countintegerResponses to those reviews.
locationRank.byReview[].response_percentnumberresponse_count as a percentage of review_count.
locationRank.byReview[].avgRatingnumberAverage star rating of the period's reviews, not counting legacy and off-platform reviews. 0 when there are none.
locationRank.byReview[].rankintegerRank by review_count, from 1. Locations with the same value share a rank.
locationRank.byRate[]array of objectsLocations ranked by avgRating, best first.
locationRank.byRate[].idintegerThe location's id (store_id).
locationRank.byRate[].namestringThe location's name, then - and its store code.
locationRank.byRate[].review_countintegerReviews in the period.
locationRank.byRate[].response_countintegerResponses to those reviews.
locationRank.byRate[].response_percentnumberresponse_count as a percentage of review_count.
locationRank.byRate[].avgRatingnumberAverage star rating of the period's reviews, not counting legacy and off-platform reviews. 0 when there are none.
locationRank.byRate[].rankintegerRank by avgRating, from 1. Locations with the same value share a rank.
locationRank.byResponse[]array of objectsLocations ranked by response_percent, best first.
locationRank.byResponse[].idintegerThe location's id (store_id).
locationRank.byResponse[].namestringThe location's name, then - and its store code.
locationRank.byResponse[].review_countintegerReviews in the period.
locationRank.byResponse[].response_countintegerResponses to those reviews.
locationRank.byResponse[].response_percentnumberresponse_count as a percentage of review_count.
locationRank.byResponse[].avgRatingnumberAverage star rating of the period's reviews, not counting legacy and off-platform reviews. 0 when there are none.
locationRank.byResponse[].rankintegerRank by response_percent, from 1. Locations with the same value share a rank.
locationRank.overAll[]array of objectsLocations ranked overall: by totalRank, lowest first.
locationRank.overAll[].idintegerThe location's id (store_id).
locationRank.overAll[].namestringThe location's name, then - and its store code.
locationRank.overAll[].review_countintegerReviews in the period.
locationRank.overAll[].response_countintegerResponses to those reviews.
locationRank.overAll[].response_percentnumberresponse_count as a percentage of review_count.
locationRank.overAll[].avgRatingnumberAverage star rating of the period's reviews, not counting legacy and off-platform reviews. 0 when there are none.
locationRank.overAll[].reviewRankintegerThe location's rank in byReview.
locationRank.overAll[].rateRankintegerThe location's rank in byRate.
locationRank.overAll[].responseRankintegerThe location's rank in byResponse.
locationRank.overAll[].totalRankintegerSum of the three ranks. Lower is better.
locationRank.overAll[].final_gradestringGrade from totalRank: A+, A, B, C or D. Fewer grades are used when there are fewer than five distinct totalRank values.
locationRank.overAll[].final_grade_pointintegerThe grade as a number: A+ 5, A 4, B 3, C 2, D 1.
locationRank.overAll[].percentage_rankstringBand such as Top 10% or Bottom 50%. Only present when there are 10 or more distinct totalRank values.

Response payload

{
  "data": {
    "account_name": "Acme Coffee",
    "interval": {
      "start": "2026-01-01 00:00:00",
      "end": "2026-09-30 23:59:59"
    },
    "locationRank": {
      "byReview": [
        {
          "id": 3007,
          "name": "Acme Coffee - Sydney",
          "review_count": 223,
          "response_count": 0,
          "response_percent": 0,
          "avgRating": 0,
          "rank": 1
        },
        {
          "id": 3008,
          "name": "Acme Coffee - Circular Quay - Sydney",
          "review_count": 0,
          "response_count": 0,
          "response_percent": 0,
          "avgRating": 0,
          "rank": 2
        }
      ],
      "byRate": [
        {
          "id": 3007,
          "name": "Acme Coffee - Sydney",
          "review_count": 223,
          "response_count": 0,
          "response_percent": 0,
          "avgRating": 0,
          "rank": 1
        },
        {
          "id": 3008,
          "name": "Acme Coffee - Circular Quay - Sydney",
          "review_count": 0,
          "response_count": 0,
          "response_percent": 0,
          "avgRating": 0,
          "rank": 1
        }
      ],
      "byResponse": [
        {
          "id": 3007,
          "name": "Acme Coffee - Sydney",
          "review_count": 223,
          "response_count": 0,
          "response_percent": 0,
          "avgRating": 0,
          "rank": 1
        },
        {
          "id": 3008,
          "name": "Acme Coffee - Circular Quay - Sydney",
          "review_count": 0,
          "response_count": 0,
          "response_percent": 0,
          "avgRating": 0,
          "rank": 1
        }
      ],
      "overAll": [
        {
          "id": 3007,
          "name": "Acme Coffee - Sydney",
          "review_count": 223,
          "response_count": 0,
          "response_percent": 0,
          "avgRating": 0,
          "reviewRank": 1,
          "rateRank": 1,
          "responseRank": 1,
          "totalRank": 3,
          "final_grade": "A+",
          "final_grade_point": 5
        },
        {
          "id": 3008,
          "name": "Acme Coffee - Circular Quay - Sydney",
          "review_count": 0,
          "response_count": 0,
          "response_percent": 0,
          "avgRating": 0,
          "reviewRank": 2,
          "rateRank": 1,
          "responseRank": 1,
          "totalRank": 4,
          "final_grade": "A",
          "final_grade_point": 4
        }
      ]
    }
  }
}

Examples

Success payload

Two locations: see Response payload. In this example the reviews are all off-platform, so avgRating is 0.

No locations to rank:

{
  "data": {
    "account_name": "Acme Coffee",
    "interval": {
      "start": "2026-01-01 00:00:00",
      "end": "2026-09-30 23:59:59"
    },
    "locationRank": {
      "byReview": [],
      "byRate": [],
      "byResponse": [],
      "overAll": []
    }
  }
}

Error payload

422 Dates 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 Date not in Y-m-d H:i:s

{
  "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 Location outside your tree

{
  "message": "The selected store id does not belong to your company hierarchy.",
  "errors": {
    "store_id": [
      "The selected store id does not belong to your company hierarchy."
    ]
  }
}

403 Not a partner, account or location user

{
  "message": "Forbidden",
  "status": "error",
  "errors": "Only partner, account or location users can access this ranking report."
}

429 Too many requests

{
  "message": "Too Many Attempts."
}
StatusMeaning
200The ranked locations, or four empty lists when there are none.
401The token is missing, expired or invalid.
403The token's user isn't a partner, account or location user.
422A date is missing or not Y-m-d H:i:s, or store_id doesn't exist or is outside your tree.
429More than 120 requests in a minute from this user.
500Unexpected server error.

Example request

curl --request POST \
  --url "https://production-api.shoutaboutus.com/api/v1/reports/ranking" \
  --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"}'

Did this page help you?