Shiny Reviews

Introduction

The Shiny Reviews API allows you to interact with your Shiny Reviews project and attached reviews. Create, search, filter, or fetch, all your reviews.

Base URL: https://[project_code].shiny.reviews

This documentation aims to provide all the information you need to work with our API.

As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile).

You can switch the language used with the tabs at the top right (or from the nav menu at the top left on mobile).

Authenticating requests

To authenticate requests, include a query parameter token in the request.

All authenticated endpoints are marked with a requires authentication badge in the documentation below.

You can retrieve your token by visiting your dashboard and going to your project settings.

Using tokens

You can create as many tokens as you need.

You can give tokens a name as well as control their "active" status. This lets you rotate tokens safely and only deactivate a token that has been replaced.

There are two types of tokens:

READ Token

READ

This token is required when you are using API endpoints that will read data. For example, listing reviews.

We use a READ token for this so that you do not expose your WRITE token to the open internet. We use these two tokens together so that you can control how data is accessed on your project.

You can create as many tokens as you want.

WRITE Token

WRITE

This token is required when you are using API endpoints that will store data. For example, storing a review.

We use a WRITE token for this so that you do not expose your reviews endpoint to the open internet unprotected. We don't want back actors filling your project with bunk reviews, spam, or unvalidated data.

We don't want back actors filling your project with bunk reviews, spam, or unvalidated data.

We recommend you the endpoints that require this token on your server. Depending on the tools you are using, you may need to implement custom endpoints so that you can validate, then store, your reviews on the server.

Here are some quick links for a few frontend frameworks:

If you are using a backend framework (Rails, Django, Laravel, etc.), I will assume you are familiar with how to call external APIs on the server in your given framework of choice.

Features

There are a variety of features in your project that can be turned on or off. This can help reduce the complexity of your usage of the API or just add some piece of mind that you aren't enabling something you don't even need.

Ratings style

Ratings style controls the type of validation that will be used when storing a rating.

The choices are:

  • Stars (Amazon style): between 1 and 5
  • Thumbs (YouTube style): either 0 or 1
  • Liked (Netflix style): 0, 50, or 100
  • Percent: between 1 and 100

If you have some more custom details to store on your review, you an use Metafields.

Review approval

Only reviews you have manually approved will be shown in the API.

Review notification

Notify the admin when a new review is created. This could be important if "review approval" is on.

Public/unprotected

The reviews and project details index/list endpoint is public and will not require a READ API token. Reviews can never be created without a WRITE API token.

Domain locked

Disallow requests to the API from the domain that does not match the domain set in your project. Be careful, as this means that you will not be able to easily test your integration on anything other than the domain provided.

For this filter, we check the Referer header on requests.

Markdown

Markdown parsing is not always perfect. So be sure to check the review content when approving it to make sure it does not have any potential XSS issues.

Parse the body of the reviews as markdown. If you don't know what markdown is, just leave this disabled.

The settings for parsing/generating markdown have "strip HTML" and "disallow unsafe links" turned on. So you can be more confident that the HTML output is more safe to display.

Webhook

Send a GET request to any valid URL once a review has been added, or if review approval is on, after it is approved.

You can use this feature to trigger builds of static sites that need to show the latest reviews or review summaries.

If your webhook URL was set to https://static-hosting.com/webhook-url, then the request to the endpoint would look something like the following:

curl --request GET \
  --url 'https://static-hosting.com/webhook-url?project=PROJECT_CODE&review=REVIEW_ID'
  --header 'X-Shiny-Reviews: PROJECT_CODE'

If the request fails, an email will be sent to the project admin about the failed request.

The Shiny Review server will attempt to send a request to the endpoint up to 3 times before giving up.

The retry is setup to delay each by 1000 additional milliseconds between requests.

If the request still fails after the 3rd attempt, an email will be sent to the project admin about the failed request.

Metafields

You can think of meta fields as custom fields. These can be simple or more complicated based on your use case.

Some use cases for metafields are as follows:

  • Tagging a review with a specific details
  • Storing the specific entity id that the review is attached to
  • Filtering a review by it's metafields
  • Finding reviews that have one or many values in a metafield

With metafields, we have a powerful mechanism for creating reviews with additional information that is important for later showing, sorting, or filtering reviews.

Simple example

Let's say we stored this review:

{
    "rating": 3,
    "title": "2009 Honda Fit",
    "body": "I loved it and I will never buy any other brand of vehicle!",
    "metafields": {
        "tag": "Car",
        "year": "2009",
        "brand": "Honda",
        "model": "Fit"
    }
}

We could find anything else matching these metafields with a URL like the following:

https://demo.shiny.reviews/api/v1/reviews?token=[my_long_token]&metafields[tag]=Car&metafields[year]=2009&metafields[brand]=Honda&metafields[model]=Fit

A request to that URL would return the reviews that had all those metafields.

Advanced tag filter

You can also filter reviews that have one metafield value OR another.

If we wanted to search for a metafield that had a tag of "Car" OR "Truck", then we can do the following:

https://demo.shiny.reviews/api/v1/reviews?token=[my_long_token]&metafields[tag][0]=Car&metafields[tag][1]=Truck

That would look like this if you were using URLSearchParams on the frontend to serialize your objects for you:

const params = new URLSearchParams({
    "metafields[tag][0]": "Car",
    "metafields[tag][1]": "Truck",
});

Sentiment analysis

Coming soon

Use AI to assign a rating for how positive or negative the review is.

Review algorithm

Coming soon

How the average rating should be calculated.

Profanity filter

Coming soon

Detect profanity.

Verified reviewer

Coming soon

Require the reviewer to use a verified email.

Image attachments

Coming soon

Allow images to be attached with the rewview.

Pre-built form widgets

Coming soon

Easily support the creation of reviews with a pre-supplied form that can be loaded in an iframe.

Endpoints

Display project details
Authenticated

List all the details for the project. Requires a read token when "Public/unprotected" is enabled.

Request   

GET
api/v1

Headers
Key Value
Content-Type
application/json
Accept
application/json
Query Parameters
Name Type Required Description Example
token
string
required
Authentication key. iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo

Example request:

curl --request GET \
    --get "https://[project_code].shiny.reviews/api/v1?token=iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://[project_code].shiny.reviews/api/v1"
);

const params = {
    "token": "iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo",
};

Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    "method": "GET",
    headers,
}).then(response => response.json());

Example response (200):

{
    "data": {
        "code": "jsrin4x03",
        "name": "Maggio, Heller and Schuppe",
        "description": "Laudantium voluptatibus voluptatum alias natus suscipit. Eaque id quo et et eius.",
        "domain": "https://www.gmail.com",
        "features": {
            "review-notification": true,
            "metafields": true,
            "sentiment-analysis": false,
            "review-approval": true,
            "public-unprotected": true,
            "domain-locked": false,
            "markdown": false
        },
        "review_stats": {
            "average": 0,
            "total_count": 0,
            "approved_count": 0
        },
        "ratings_style": "stars",
        "ratings_style_options": [
            1,
            2,
            3,
            4,
            5
        ]
    }
}
Display project details
Authenticated

List all the details for the project. Requires a read token when "Public/unprotected" is enabled.

Request   

GET
api/v1/project

Headers
Key Value
Content-Type
application/json
Accept
application/json
Query Parameters
Name Type Required Description Example
token
string
required
Authentication key. iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo

Example request:

curl --request GET \
    --get "https://[project_code].shiny.reviews/api/v1/project?token=iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://[project_code].shiny.reviews/api/v1/project"
);

const params = {
    "token": "iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo",
};

Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    "method": "GET",
    headers,
}).then(response => response.json());

Example response (200):

{
    "data": {
        "code": "dk5o6f2uq",
        "name": "Maggio, Heller and Schuppe",
        "description": "Laudantium voluptatibus voluptatum alias natus suscipit. Eaque id quo et et eius.",
        "domain": "https://www.gmail.com",
        "features": {
            "review-notification": true,
            "metafields": true,
            "sentiment-analysis": false,
            "review-approval": true,
            "public-unprotected": true,
            "domain-locked": false,
            "markdown": false
        },
        "review_stats": {
            "average": 0,
            "total_count": 0,
            "approved_count": 0
        },
        "ratings_style": "stars",
        "ratings_style_options": [
            1,
            2,
            3,
            4,
            5
        ]
    }
}
Summarize reviews
Authenticated

Summarize all the reviews in the project based on a filter. Requires a read token when "Public/unprotected" is not enabled.

Request   

GET
api/v1/reviews/summary

Headers
Key Value
Content-Type
application/json
Accept
application/json
Query Parameters
Name Type Required Description Example
token
string
required
Authentication key. iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo
Body Parameters
Name Type Required Description Example
metafields
object
optional
The metafields to filter by. {"make":"Honda"}

Example request:

curl --request GET \
    --get "https://[project_code].shiny.reviews/api/v1/reviews/summary?token=iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"metafields\": {
        \"make\": \"Honda\"
    }
}"
const url = new URL(
    "https://[project_code].shiny.reviews/api/v1/reviews/summary"
);

const params = {
    "token": "iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo",
};

Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "metafields": {
        "make": "Honda"
    }
};

fetch(url, {
    "method": "GET",
    headers,
    "body": JSON.stringify(body),
}).then(response => response.json());

Example response (200):

{
    "total_reviews": 184,
    "average_rating": 2.462
}
Approve a review
Authenticated

Used for approving reviews through emails. Requires a signed URL. Currently, this cannot be called directly.

Request   

GET
api/v1/reviews/{review_id}/approve

Headers
Key Value
Content-Type
application/json
Accept
application/json
URL Parameters
Name Type Required Description Example
review_id
integer
required
The ID of the review. 1
Query Parameters
Name Type Required Description Example
token
string
required
Authentication key. iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo

Example request:

curl --request GET \
    --get "https://[project_code].shiny.reviews/api/v1/reviews/1/approve?token=iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://[project_code].shiny.reviews/api/v1/reviews/1/approve"
);

const params = {
    "token": "iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo",
};

Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    "method": "GET",
    headers,
}).then(response => response.json());

Example response (200):

{
    "data": {
        "id": 727,
        "rating": 0,
        "title": "Eos voluptas molestias aut laudantium voluptatibus voluptatum alias natus.",
        "body": "Odit eaque id quo et et eius voluptatem. Accusantium aliquam dicta et et labore voluptate doloremque voluptatem. Vitae porro voluptatum odio. Beatae numquam voluptate et omnis in. Blanditiis et laudantium aut porro et.",
        "metafields": null,
        "created_at": "2024-11-11T23:51:16.000000Z"
    }
}

Example response (302, review approved successfully):

Empty response
List reviews
Authenticated

List all the reviews in the project. Results are paginated. Requires a read token when "Public/unprotected" is not enabled.

Request   

GET
api/v1/reviews

Headers
Key Value
Content-Type
application/json
Accept
application/json
Query Parameters
Name Type Required Description Example
token
string
required
Authentication key. iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo
Body Parameters
Name Type Required Description Example
metafields
object
optional
The metafields to filter by. {"tag":"Product"}
positive
boolean
optional
If you want to only show the positive reviews. Requires "sentiment analysis" to be enabled. true
negative
boolean
optional
If you want to only show the negative reviews. Requires "sentiment analysis" to be enabled. false

Example request:

curl --request GET \
    --get "https://[project_code].shiny.reviews/api/v1/reviews?token=iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"metafields\": {
        \"tag\": \"Product\"
    },
    \"positive\": true,
    \"negative\": false
}"
const url = new URL(
    "https://[project_code].shiny.reviews/api/v1/reviews"
);

const params = {
    "token": "iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo",
};

Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "metafields": {
        "tag": "Product"
    },
    "positive": true,
    "negative": false
};

fetch(url, {
    "method": "GET",
    headers,
    "body": JSON.stringify(body),
}).then(response => response.json());

Example response (200):

{
    "data": [
        {
            "id": 728,
            "rating": 0,
            "title": "Eos voluptas molestias aut laudantium voluptatibus voluptatum alias natus.",
            "body": "Odit eaque id quo et et eius voluptatem. Accusantium aliquam dicta et et labore voluptate doloremque voluptatem. Vitae porro voluptatum odio. Beatae numquam voluptate et omnis in. Blanditiis et laudantium aut porro et.",
            "metafields": null,
            "created_at": "2024-11-11T23:51:16.000000Z"
        },
        {
            "id": 729,
            "rating": 0,
            "title": "Aperiam blanditiis voluptatem officia magnam rerum minima.",
            "body": "Quam nisi quisquam sit quos. Laboriosam deleniti minima vitae assumenda rerum. Ut illum accusamus illo et et aperiam. Repellendus nostrum amet quo molestiae. Et ea numquam totam maxime ea provident magni quisquam.",
            "metafields": null,
            "created_at": "2024-11-11T23:51:16.000000Z"
        }
    ],
    "links": {
        "first": "/?page=1",
        "last": "/?page=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "links": [
            {
                "url": null,
                "label": "« Previous",
                "active": false
            },
            {
                "url": "/?page=1",
                "label": "1",
                "active": true
            },
            {
                "url": null,
                "label": "Next »",
                "active": false
            }
        ],
        "path": "/",
        "per_page": 10,
        "to": 2,
        "total": 2
    }
}
Store a review
Authenticated

Store a single review. Requires a write token.

Request   

POST
api/v1/reviews

Headers
Key Value
Content-Type
application/json
Accept
application/json
Query Parameters
Name Type Required Description Example
token
string
required
Authentication key. iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo
Body Parameters
Name Type Required Description Example
rating
integer
required
The rating for this review. 3
title
string
required
The title for this review. 2009 Honda Fit
body
string
required
The body for this review. I loved it and I will never buy any other brand of vehicle!
metafields
object
optional
The metafields to filter by. {"tag":"Car","author":"Rando","approved":true}

Example request:

curl --request POST \
    "https://[project_code].shiny.reviews/api/v1/reviews?token=iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"rating\": 3,
    \"title\": \"2009 Honda Fit\",
    \"body\": \"I loved it and I will never buy any other brand of vehicle!\",
    \"metafields\": {
        \"tag\": \"Car\",
        \"author\": \"Rando\",
        \"approved\": true
    }
}"
const url = new URL(
    "https://[project_code].shiny.reviews/api/v1/reviews"
);

const params = {
    "token": "iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo",
};

Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "rating": 3,
    "title": "2009 Honda Fit",
    "body": "I loved it and I will never buy any other brand of vehicle!",
    "metafields": {
        "tag": "Car",
        "author": "Rando",
        "approved": true
    }
};

fetch(url, {
    "method": "POST",
    headers,
    "body": JSON.stringify(body),
}).then(response => response.json());

Example response (200):

{
    "data": {
        "id": 730,
        "rating": 0,
        "title": "Voluptatibus eveniet voluptatem mollitia et magni nam cupiditate.",
        "body": "Voluptatem ea numquam eos architecto. Aspernatur dolore et dignissimos quibusdam consectetur eos voluptas molestias. Laudantium voluptatibus voluptatum alias natus suscipit. Eaque id quo et et eius. Vel accusantium aliquam dicta et et labore voluptate doloremque.",
        "metafields": null,
        "created_at": "2024-11-11T23:51:16.000000Z"
    }
}
Show a review
Authenticated

Show the details for a single review. Requires a read token when "Public/unprotected" is not enabled.

Request   

GET
api/v1/reviews/{id}

Headers
Key Value
Content-Type
application/json
Accept
application/json
URL Parameters
Name Type Required Description Example
id
integer
required
The ID of the review. 1
Query Parameters
Name Type Required Description Example
token
string
required
Authentication key. iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo

Example request:

curl --request GET \
    --get "https://[project_code].shiny.reviews/api/v1/reviews/1?token=iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://[project_code].shiny.reviews/api/v1/reviews/1"
);

const params = {
    "token": "iT1fTUMRpZxgMaTxo1R2xyBc4KW8WF2abXF5hNNo",
};

Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    "method": "GET",
    headers,
}).then(response => response.json());

Example response (200):

{
    "data": {
        "id": 731,
        "rating": 0,
        "title": "Eos voluptas molestias aut laudantium voluptatibus voluptatum alias natus.",
        "body": "Odit eaque id quo et et eius voluptatem. Accusantium aliquam dicta et et labore voluptate doloremque voluptatem. Vitae porro voluptatum odio. Beatae numquam voluptate et omnis in. Blanditiis et laudantium aut porro et.",
        "metafields": null,
        "created_at": "2024-11-11T23:51:16.000000Z"
    }
}