Importing reviews
Bring reviews a brand already has — from Google, Trustpilot, G2, Capterra, a Senja or Testimonial.to account, a spreadsheet — into Akteora. Each becomes a text testimonial, labelled with where it came from, unapproved and private until someone publishes it.
The dashboard does this from a CSV file (Inbox → Import reviews); the step-by-step guide for customers, including saving from Excel, Google Sheets and Numbers, is at akteora.com/guides/import-reviews. The API takes the same rows as JSON, so an integration can import straight from its own data without a file.
What an imported review is
| Collected through a link | Imported | |
|---|---|---|
origin |
collected |
imported |
collect_link_id |
the link | null |
source |
null |
{ "name": "Google", "url": "https://…" } |
reviewed_at |
null |
when the customer originally wrote it, if you said |
kind |
video, audio or text |
always text |
consent (detail) |
what the customer agreed to, word for word | always null |
| Arrives | unapproved, private | unapproved, private |
| Counts against | submissions_per_month |
imported_reviews |
These hold everywhere, and are not settings:
- The words are kept exactly as sent. They are the testimonial's
originaltext, which nobody — the organisation included — can edit afterwards. - It is always labelled. The widget, the public wall and its permalink pages show
"Imported from Google", linked to
source.urlwhen there is one, whatever else the widget is set to show. Exports carryorigin,sourceandreviewed_at. - Its rating is never averaged. An imported review shows its own stars, but the wall's
rating, the widget heading's rating and the wall's
AggregateRatingmarkup count collected testimonials only. It is never marked up as a Schema.orgReview, which Google's policy forbids for reviews copied from another site. - No consent record is invented. The customer never saw your consent wording, and the API does not pretend they did.
- No webhooks. Importing sends no
submission.createdorsubmission.ready: those mean a customer has just sent something. Approving an imported review later sendssubmission.approvedas usual, withorigin: "imported"andlink_id: null.
Why it works this way is recorded in ADR 0014.
Import rows
curl -H "Authorization: Bearer $AKTEORA_API_KEY" -H 'content-type: application/json' \
-d '{
"brand_id": "brd_…",
"source": { "name": "Google", "url": "https://g.page/r/your-business" },
"attested": true,
"rows": [
{
"text": "They rebuilt our onboarding in two weeks.",
"name": "Maria López",
"title": "Head of Operations",
"company": "Fernhill Studio",
"rating": 5,
"reviewed_at": "2025-03-14",
"source_url": "https://g.page/r/your-business/review/1"
}
]
}' \
https://api.akteora.com/v1/imports
201 Created:
{ "import_id": "imp_…", "created": 1, "duplicate_rows": [], "origin": "imported" }
Needs the submissions:write scope. A signed-in member must be an owner or admin
(submission:import): importing is vouching for content on the organisation's behalf.
The body
| Field | ||
|---|---|---|
brand_id |
required | The brand the reviews are about |
source.name |
required | Where they were first published, up to 60 characters: Google, Trustpilot, Spreadsheet… |
source.url |
optional | The brand's page there, http(s) |
attested |
required, true |
See below |
rows |
1 to 500 | More than 500? Send several requests. Re-sending is safe |
Each row:
| Field | ||
|---|---|---|
text |
required | The review, 1–5,000 characters after trimming |
name |
optional | Up to 120 characters |
email |
optional | Never shown publicly |
title, company |
optional | Up to 120 characters each |
rating |
optional | A whole number, 1 to 5. 4.5 is refused, never rounded |
reviewed_at |
optional | 2025-03-14, or an RFC 3339 timestamp. Not in the future |
source_url |
optional | The review's own page, http(s) |
source |
optional | This row's source, when it differs from source.name |
The attestation
attested: true means agreeing, on the organisation's behalf, to exactly this:
These are genuine reviews written by real customers. I am copying them without changing their words, and I have the right to use them.
The wording is stored with the import, with the member or API key that sent it and when, and the import is written to the organisation's audit log. There is no import without it.
What refuses an import
- Any invalid row refuses the whole request with
422, and nothing is written. Each problem names its row:rows.3.ratingis the fourth row's rating. - A review dated in the future is a
422naming the row. - Over the plan's allowance is
402plan_limit_exceeded, withlimit.nameimported_reviews. Only reviews that would be created count — duplicates do not. - A brand that is not yours is
404.
Sending the same rows again
A review already imported for the brand — the same source, name and words, ignoring case and
spacing — is skipped, and its 1-based position is in duplicate_rows. It is not an error. So an
import interrupted halfway can be sent again from the start, and the second run creates only
what the first did not.
List imports
curl -H "Authorization: Bearer $AKTEORA_API_KEY" https://api.akteora.com/v1/imports
The latest 50, newest first, each with created_count, duplicate_count, the
attestation_text agreed to, and who agreed: attested_by_user_id or api_key_id.
Needs submissions:read.
Find imported reviews
curl -H "Authorization: Bearer $AKTEORA_API_KEY" \
'https://api.akteora.com/v1/submissions?origin=imported&is_approved=false'
origin also filters exports. Publish one as you would any testimonial:
POST /v1/submissions/{id}/approve, then POST /v1/submissions/{id}/visibility.
Allowances
imported_reviews is a standing total, not monthly: Free 50, Starter 500, Core 5,000, Plus
unlimited. Test mode allows 100. Deleting an imported review frees its place.
The CSV the dashboard reads
The dashboard's importer turns a CSV into these rows in the browser; the file itself is never
uploaded. UTF-8 (a file in another encoding is refused, not guessed at), separated by commas,
semicolons or tabs — detected from the header line, so European Excel's semicolons work — with a
header row and one review per row. Header names are matched
ignoring case, spaces, - and _, and can be corrected on screen:
| Field | Headers recognised |
|---|---|
text |
text, review, testimonial, comment, content, body, feedback, message, quote |
name |
name, author, reviewer, customer, full name, reviewer name |
rating |
rating, stars, score, star rating — also accepts 4/5, 4 stars and ★★★★☆ |
reviewed_at |
date, review date, reviewed at, created, published — also accepts 14 March 2025 and March 14, 2025. Not 03/04/2025, which is ambiguous |
title |
title, job title, role, position |
company |
company, organization, organisation, business |
email |
email, email address |
source_url |
source url, url, link, review url, permalink |
source |
source, platform, site |
A template is at https://app.akteora.com/akteora-import-template.csv.