# Customer Testimonials

https://dev.wisecp.com/es/customer-testimonials

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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/website/cfeedbacks' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
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');
```

```php
$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);
```

```php
// 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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/website/cfeedbacks/4' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res  = await fetch(`https://panel.example.com/api/v1/admin/website/cfeedbacks/${id}`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
```

```php
$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);
```

```php
// 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 JavaScript PHP (HTTP) PHP (Internal)

```bash
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!"}}}'
```

```javascript
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();
```

```php
$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);
```

```php
// 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 JavaScript PHP (HTTP) PHP (Internal)

```bash
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"}'
```

```javascript
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();
```

```php
$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);
```

```php
// 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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X DELETE 'https://panel.example.com/api/v1/admin/website/cfeedbacks/4' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
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();
```

```php
$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);
```

```php
// 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 JavaScript PHP (HTTP) PHP (Internal)

```bash
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"}'
```

```javascript
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();
```

```php
$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);
```

```php
// 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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X DELETE 'https://panel.example.com/api/v1/admin/website/cfeedbacks/4/picture' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
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();
```

```php
$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);
```

```php
// 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.

## Related Articles

- [Website Pages](https://dev.wisecp.com/en/website-pages)
- [Homepage Slides](https://dev.wisecp.com/en/homepage-slides)
- [The Contact Inbox](https://dev.wisecp.com/en/contact-inbox)
