Customer Testimonials

7 views Markdown

The seven endpoints that add, approve and remove the testimonials shown on the site.

Overview

Testimonials are the reference texts shown on the site. Each carries a status: pending, approved or rejected. Only the approved ones appear on the site.

The text is kept per language, while the name, company and avatar are shared. What a visitor submits starts as pending and stays off the site until an administrator approves it.

These endpoints also let you add a testimonial through the panel: a reference that arrived on paper or by e-mail can be typed in and created approved outright.

Reference

Listing the Testimonials

get/api/v1/admin/website/cfeedbacks
Website/GetCfeedbacks admin

Returns the customer testimonials on the site.

Query 3
searchstringSearches the name, company, e-mail and sending address.
pageintWhich page.
limitintRecords per page. A hundred at most.
Response fields data[] — 7 + meta — 4
idintThe testimonial id.
full_namestringThe name of whoever left it.
company_namestringThe person's company.
emailstringTheir e-mail address.
statusstringThe testimonial status: pending, approved, rejected.
rankintWhere it sits in the listing.
created_atstring | nullWhen it was submitted.
totalintHow many there are. It comes back under meta.
pageintThe page you are on.
limitintThe page size.
next_pageintThe next page. Zero means you are on the last one.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/website/cfeedbacks' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/website/cfeedbacks', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const { data } = await res.json();

const waiting = data.filter((f) => f.status === 'pending');
$ch = curl_init('https://panel.example.com/api/v1/admin/website/cfeedbacks');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// There is NO status filter; sift the list yourself to find what waits for a decision.
$all     = Api::Website()->GetCfeedbacks()['data'];
$waiting = array_filter($all, fn ($f) => $f['status'] === 'pending');

Reading One Testimonial

get/api/v1/admin/website/cfeedbacks/{id}
Website/GetCfeedback admin

Returns one testimonial with its text and avatar.

Response fields data — 11
idintThe testimonial id.
full_namestringThe name of whoever left it.
company_namestringThe person's company.
emailstringTheir e-mail address.
statusstringThe testimonial status: pending, approved, rejected.
rankintWhere it sits in the listing.
unreadintThe unread mark. It is one on an approved testimonial.
ipstringThe address it came from.
created_atstring | nullWhen it was submitted.
languagesobjectThe testimonial text per language.
picturestring | nullThe avatar address.
Errors 2
cfeedback_not_found404No such testimonial.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/website/cfeedbacks/4' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch(`https://panel.example.com/api/v1/admin/website/cfeedbacks/${id}`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/website/cfeedbacks/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The TEXT and the sending ADDRESS come back only here; the list carries neither.
$fb = Api::Website()->GetCfeedback(['id' => $id])['data'];
$en = $fb['languages']['en']['message'] ?? '';

Creating a Testimonial

post/api/v1/admin/website/cfeedbacks
Website/CreateCfeedback admin

Adds a customer testimonial through the panel.

Body 7
full_namestringreqThe name of whoever left it.
languagesobjectreqThe testimonial text per language. Text is needed in each language you send.
company_namestringThe person's company.
emailstringTheir e-mail address. It is not shown on the site.
statusstringThe testimonial status: pending, approved, rejected. Pending by default.
rankintWhere it sits in the listing.
imagestringThe avatar to upload in the same call.
Response fields 201 — data — 11
dataobjectThe testimonial created. Same shape as the detail endpoint.
Errors 6
full_name_required422The name is empty.
languages_required422No language was sent.
message_required422The text is empty in one language.
invalid_status422The status is not recognised.
create_failed500The testimonial could not be created.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/website/cfeedbacks' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"full_name":"Ayse Yilmaz","status":"approved","languages":{"tr":{"message":"Harika hizmet!"}}}'
const res = await fetch('https://panel.example.com/api/v1/admin/website/cfeedbacks', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    full_name: 'John Doe',
    company_name: 'Example Inc.',
    status: 'approved',
    languages: { en: { message: 'Great service!' } },
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/website/cfeedbacks');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'full_name' => 'John Doe',
        'status'    => 'approved',
        'languages' => ['en' => ['message' => 'Great service!']],
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Leave the status out and it is born PENDING, unseen on the site; approve it as well.
Api::Website()->CreateCfeedback([
    'full_name' => 'John Doe',
    'status'    => 'approved',
    'languages' => ['en' => ['message' => 'Great service!']],
]);

Updating a Testimonial

patch/api/v1/admin/website/cfeedbacks/{id}
Website/UpdateCfeedback admin approval happens here

Changes a testimonial's status, text and details.

Body 7
full_namestringThe name of whoever left it. Left out, the current value is kept.
languagesobjectreqThe testimonial text per language. Text is needed in each language you send.
company_namestringThe person's company.
emailstringTheir e-mail address. It is not shown on the site.
statusstringThe testimonial status: pending, approved, rejected. Pending by default.
rankintWhere it sits in the listing.
imagestringThe avatar to upload in the same call.
Response fields data — 11
dataobjectThe testimonial as it now stands. Same shape as the detail endpoint.
Errors 5
cfeedback_not_found404No such testimonial.
full_name_required422The name was emptied.
message_required422The text was emptied in one language.
invalid_status422The status is not recognised.
insufficient_scope403The key lacks the required scope.
Request
curl -X PATCH 'https://panel.example.com/api/v1/admin/website/cfeedbacks/4' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"status":"approved"}'
const res = await fetch(`https://panel.example.com/api/v1/admin/website/cfeedbacks/${id}`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ status: 'approved' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/website/cfeedbacks/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['status' => 'approved']),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Approval is a STATUS change rather than a switch; it is the one thing that publishes.
Api::Website()->UpdateCfeedback(['id' => $id, 'status' => 'approved']);

Deleting a Testimonial

delete/api/v1/admin/website/cfeedbacks/{id}
Website/DeleteCfeedback admin

Removes a testimonial.

Response fields data — 2
deletedboolWhether the delete ran.
idintThe id of the testimonial removed.
Errors 3
cfeedback_not_found404No such testimonial.
delete_failed500The testimonial could not be removed.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/website/cfeedbacks/4' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch(`https://panel.example.com/api/v1/admin/website/cfeedbacks/${id}`, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/website/cfeedbacks/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Rather than delete, REJECT it: the record stays off the site while the words survive.
Api::Website()->UpdateCfeedback(['id' => $id, 'status' => 'rejected']);

Uploading the Avatar

put/api/v1/admin/website/cfeedbacks/{id}/picture
Website/SetCfeedbackPicture admin

Uploads the avatar shown beside a testimonial.

Body 1
imagestringreqThe avatar to upload. Either an address or the data itself.
Response fields 201 — data — 1
urlstringThe avatar's public address.
Errors 2
cfeedback_not_found404No such testimonial.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/website/cfeedbacks/4/picture' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"image":"https://ornek.com/avatar.jpg"}'
const res = await fetch(`https://panel.example.com/api/v1/admin/website/cfeedbacks/${id}/picture`, {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ image: 'https://example.com/avatar.jpg' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/website/cfeedbacks/' . $id . '/picture');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['image' => 'https://example.com/avatar.jpg']),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The avatar is NOT required; without one the theme puts its own placeholder there.
Api::Website()->SetCfeedbackPicture(['id' => $id, 'image' => $data]);

Removing the Avatar

delete/api/v1/admin/website/cfeedbacks/{id}/picture
Website/DeleteCfeedbackPicture admin

Removes a testimonial's avatar.

Response fields data — 2
deletedboolWhether the delete ran.
idintThe testimonial id.
Errors 2
cfeedback_not_found404No such testimonial.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/website/cfeedbacks/4/picture' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch(`https://panel.example.com/api/v1/admin/website/cfeedbacks/${id}/picture`, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/website/cfeedbacks/' . $id . '/picture');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// To REPLACE the avatar there is no need to delete first; the upload takes its place.
Api::Website()->DeleteCfeedbackPicture(['id' => $id]);

Pitfalls

A new testimonial is born pending

Leave the status field out and the testimonial is created pending, unseen on the site. For a reference typed in through the panel that is rarely what you want, so send the status approved at creation or update it straight after. For what a visitor submits, pending is the right start.

You cannot filter by status

The listing takes only a search term, a page and a page size, and offers no status filter. Finding what waits for approval means pulling the list and sifting it yourself. On an installation with many testimonials, plan for walking the pages and sifting each one.

The text is not in the list

The list gives the name, company, e-mail and status, yet not the testimonial itself. Deciding what to approve means reading each record from the detail endpoint. Weigh that when writing an approval flow: the list alone cannot carry the decision.

The e-mail and the address stay on the record

A testimonial record carries the sender's e-mail and the address it came from. Neither is shown on the site, yet both can be read from the detail endpoint. That is personal data, so take care not to carry these two fields along when writing an integration that moves testimonials elsewhere.

Reject rather than delete

A rejected testimonial stays off the site while the record remains: who wrote what, when, and what was decided all stay readable. Deleting takes all of that, and if the same person writes again you have no history to look at.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.