02 Get your data in /Integrations
REVIEWS.io
Send REVIEWS.io company and product reviews to Dimension Labs with the universal API.
- RI REVIEWS.ioTickets, chats, calls, reviews
- ⟳ Your jobPull new · map fields · every 15 min
- { } Universal APIPOST /track · /trackhistorical
- Dimension LabsRead in full · enriched daily
This brings in the reviews in your REVIEWS.io store, company reviews and product reviews: each review as a customer message, with its rating and product, and your public replies when there are any. One conversation per review. You run it on your side: a small script pulls from the REVIEWS.io API and posts each message to the universal API.
What you need
Section titled “What you need”- The REVIEWS.io Plus plan. REVIEWS.io says API access is only available on Plus.
- Your REVIEWS.io public store key and private API key. The private key adds private data to responses and is needed to list every review type.
- A Dimension Labs API key for a new Universal API source. See Your API key.
1. Get API access in REVIEWS.io
Section titled “1. Get API access in REVIEWS.io”REVIEWS.io’s steps are in REVIEWS.io API.
- Log in to your REVIEWS.io account.
- Go to Integrations, then select API from the integrations library. Copy your API key and keep it in a secrets manager. You also need your public store key, which REVIEWS.io’s request examples call the store ID.
- The API takes both as query parameters:
store(the public store key) andapikey(the private key). Call it from your server only.
2. Pull new reviews
Section titled “2. Pull new reviews”curl "https://api.reviews.io/reviews?store=YOUR_STORE&apikey=$REVIEWSIO_API_KEY&type=store_review&dateFrom=2026-10-01&sort=date_asc&per_page=100&page=1"- “Retrieve Reviews and Questions” is the current endpoint. The older “Retrieve Company Reviews” and “Retrieve Product Reviews” endpoints are marked legacy and deprecated.
- Types:
type=store_reviewfor company reviews,type=product_reviewfor product reviews. The code calls each in turn. With notype, the endpoint also returns third-party reviews and questions, and requires the API key. - Incremental:
dateFromreturns reviews newer than a date. Sort withsort=date_asc. There is no “updated since” filter on this endpoint. - Pagination:
pageandper_page.results_countis the total with your filters applied. - Public replies are in the same review, under
replies, so there is no second call. - Rate limits: REVIEWS.io doesn’t publish one, and asks you to handle errors because its API can have short downtime. The code waits and retries on
429and5xx.
3. Map the fields
Section titled “3. Map the fields”Each review is one incoming message under its own sessionId. Each public reply is an outgoing message in the same session.
| Dimension Labs | REVIEWS.io | Notes |
|---|---|---|
text | title and comments (incoming), replies[].comments (outgoing) | Join the title and body with a line break. Reviews with no text are skipped. |
userId | REVIEWS.io returns no reviewer id, so use the sessionId. Don’t send author.name. | |
incoming | true for the review, false for a reply. | |
sessionId | id | Already unique across types (for example store_review-123456). Prefixed reviews-io-. |
conversationId | id | |
dimensionlabs_timestamp | date_created (review and reply) | Convert with toMillis, which reads a date without a time zone as UTC. A reply within a second of the review is moved a second later. |
platformJson | type, rating, sku, order_id, source, tags, attributes | order_id is your order reference and sku your product identifier: the fields to join to your own data. |
Personal data. With the API key, REVIEWS.io adds private data such as reviewer email addresses. Don’t send it, or the reviewer’s name and location: the code leaves the author object out. Reviews can still name people in their text: see PII redaction.
4. Send it
Section titled “4. Send it”// reviews-io.mjs. Environment: REVIEWSIO_STORE, REVIEWSIO_API_KEY, DL_API_KEY.import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
const HOUR = 60 * 60 * 1000;const DAY = 24 * HOUR;const TYPES = ['store_review', 'product_review'];const sleep = (ms) => new Promise((r) => setTimeout(r, ms));const at = (d, fallback) => { try { return d ? toMillis(d) : fallback; } catch { return fallback; } };
// Retries REVIEWS.io's rate limits (429) and server errors (5xx), honoring Retry-After when it is sent.async function get(url) { for (let attempt = 0; ; attempt++) { const res = await fetch(url, { headers: { Accept: 'application/json' } }); if (res.ok) return res.json(); if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`REVIEWS.io returned ${res.status}: ${await res.text()}`); await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000); }}
async function send(r, historical) { const id = `reviews-io-${String(r.id).replace(/:/g, '-')}`; const created = toMillis(r.date_created); const base = { userId: id, sessionId: id, conversationId: id, platformJson: { type: r.type, rating: r.rating, sku: r.sku, orderId: r.order_id, source: r.source, tags: r.tags, attributes: r.attributes } }; const text = [r.title, r.comments].filter((x) => x?.trim()).join('\n'); if (text) { await sendToDimensionLabs({ ...base, text, incoming: true, dimensionlabs_timestamp: created }, { historical: historical || created < Date.now() - HOUR }); } let last = created; for (const reply of r.replies ?? []) { if (!reply.comments?.trim()) continue; last = Math.max(at(reply.date_created, last), last + 1000); await sendToDimensionLabs({ ...base, text: reply.comments.trim(), incoming: false, dimensionlabs_timestamp: last }, { historical: historical || last < Date.now() - HOUR }); }}
// Sends every company and product review created from the day before `since` (Unix ms). Returns the new `since`.export async function syncReviewsIo({ since, historical = false }) { const started = Date.now(); const dateFrom = new Date(since - DAY).toISOString().slice(0, 10); // dateFrom takes a date: start a day early for (const type of TYPES) { for (let page = 1; ; page++) { const q = new URLSearchParams({ store: process.env.REVIEWSIO_STORE, apikey: process.env.REVIEWSIO_API_KEY, type, dateFrom, sort: 'date_asc', per_page: '100', page: String(page) }); const { reviews = [] } = await get(`https://api.reviews.io/reviews?${q}`); for (const r of reviews) await send(r, historical); if (!reviews.length) break; } } return started;}Each run asks for every review from the day before the cursor, so a day of reviews is sent again on each run: those repeats are duplicates, removed automatically. This picks reviews up by creation date, so a reply written after the review was pulled is not sent. To catch those, run a history load over the last few weeks now and then (node run.mjs with a recent date): it sends the new replies, and everything else in it is a duplicate. Then run a backfill.
5. Run it
Section titled “5. Run it”Save the code as reviews-io.mjs in a project set up as in Set up your project, and run it with the scheduled sync runner: import { syncReviewsIo as sync } from './reviews-io.mjs';.
- History first:
node run.mjs 2020-01-01sends every review since that date to/trackhistorical. Then start a backfill from the Backfill tab. - Then every 15 minutes: the scheduled job sends new reviews to
/track, and they are enriched automatically every day. Anything more than an hour old when the job sees it goes to/trackhistorical, and needs a backfill. - Re-runs are safe: duplicates are removed automatically.
If you aren’t on the Plus plan, or you have reviews in a file instead, upload it and check it first with the file checker, then run a backfill.
REVIEWS.io reference
Section titled “REVIEWS.io reference”- REVIEWS.io API (help center)
- Retrieve Reviews and Questions
- Retrieve Company Reviews (legacy)
- Retrieve Product Reviews (legacy)
Checked against REVIEWS.io’s documentation on 2026-10-26.