---
name: showtrust
description: Install a ShowTrust testimonials wall of love into the user's website. Use when the user asks to add testimonials, social proof, customer reviews, or a "wall of love" to their site, OR mentions ShowTrust by name. Signs the user up if they don't have an account, configures the widget, fetches the embed snippet, and injects it into the right file for their framework.
---

# ShowTrust — install a wall of love

ShowTrust is a hosted testimonials platform with an embeddable widget. This skill lets you sign the user up (just from their email), create a project, configure the widget, and inject the embed snippet into the user's codebase.

## When to use this skill

The user asked for one of:

- "Add a wall of love" / "add testimonials" / "add customer reviews"
- "Install ShowTrust" / mentions ShowTrust by name
- "Show off the trust" / wants social proof on their site

## API base URL

- Production: `https://api.showtrust.to`

## Authentication

Every request to `/v1/*` requires two headers:

```
x-showtrust-account-id: $SHOWTRUST_ACCOUNT_ID
x-api-key: $SHOWTRUST_API_KEY
```

All responses use a `{ "message": "...", "data": ... }` envelope. Treat `data` as the real payload.

**Exception:** `POST /v1/auth/agent-init` is public — no headers required. You call this once at the start of the flow to obtain the account ID + API key.

## Standard install flow

Follow these steps in order. Stop and surface any error to the user — do not continue past a 4xx/5xx response.

### Step 1: Get the user's email and project name

Ask the user for **two** things up front, in one message:

1. **Email** — even if you can infer it from git config, ask first so they can use a different inbox if they prefer.
2. **Project / business name** — this is what their wall of love will be branded as, and it determines the public collection URL (`https://app.showtrust.to/<slug>`). Don't try to infer it silently from `package.json` and proceed — the package name is often a tech slug (`my-app-v2`) that makes for a terrible brand on a customer-facing page. If `package.json` has a plausible name, *propose* it and ask the user to confirm or replace it; otherwise ask outright.

Example opening:

> *"To install ShowTrust I need two things: (1) your email — I'll use it to sign you up, and (2) the project / business name that should appear on your wall of love (this also becomes your public collection URL, e.g. `app.showtrust.to/acme`). Your `package.json` has `name: "acme-web"` — should I use **Acme Web**, or something else?"*

Hold onto the project name — you'll use it as `businessName` (and to derive `slug`) when you call `POST /v1/projects` in step 6.

### Step 2: Sign in (or sign up) via agent-init

```bash
curl -X POST \
     -H "Content-Type: application/json" \
     -d '{"email":"'$USER_EMAIL'"}' \
     https://api.showtrust.to/v1/auth/agent-init
```

Two possible responses:

**A. New user — `claimRequired: true`:**

```json
{
  "message": "Success",
  "data": {
    "accountId": "65f0...",
    "apiKey": "st_live_...",
    "claimRequired": true,
    "expiresInDays": 3
  }
}
```

Credentials are **active immediately**. Save `accountId` and `apiKey` to `.env.local` and continue to step 3. The user will receive a "claim your account" email with a link to set a password. Their widget stays live for 3 days unclaimed — after that the project is cleaned up.

> **Key rotation on retry:** if you call agent-init again for the same email while the account is still unclaimed (e.g. the agent crashed and you're retrying), the previous `apiKey` is invalidated and a fresh one is issued. Always overwrite the old `.env.local` values with whatever's in the latest response.

**B. Existing account — `accountExists: true`:**

```json
{
  "message": "Success",
  "data": {
    "claimRequired": false,
    "accountExists": true,
    "message": "Account already exists. Paste your API key from Settings → API to continue."
  }
}
```

The user already has a ShowTrust account, so we don't create a duplicate. **No key is issued — the user must paste their existing one.** Tell the user clearly:

> *"You already have a ShowTrust account. Open https://app.showtrust.to/ → **Settings → API**, copy your **Account ID** and **API key**, then paste both back here. I'll continue once you have them."*

When the user pastes them, save to `.env.local` and continue to step 4 (skip step 3 — credentials came from the user, not from this response).

If the user can't find or doesn't know their key, they can regenerate it from the same Settings → API page. Note that regenerating invalidates any prior key.

**C. Rate limit (429):**
Wait an hour, or sign up manually at https://app.showtrust.to/.

### Step 3: Write credentials to .env.local

Add to (or create) `.env.local`:

```
SHOWTRUST_ACCOUNT_ID=...
SHOWTRUST_API_KEY=st_live_...
```

**Never write these to a file that gets committed.** Make sure `.env.local` is in `.gitignore` (add it if not).

### Step 4: Confirm credentials work

```bash
curl -H "x-showtrust-account-id: $SHOWTRUST_ACCOUNT_ID" \
     -H "x-api-key: $SHOWTRUST_API_KEY" \
     https://api.showtrust.to/v1/me
```

Expect `200`. On `401` → credentials are wrong. Ask the user to double-check what they pasted, or have them regenerate from Settings → API.

### Step 5: Check for existing projects (avoid duplicates)

Before creating a new project, list what's already there:

```bash
curl -H "x-showtrust-account-id: $SHOWTRUST_ACCOUNT_ID" \
     -H "x-api-key: $SHOWTRUST_API_KEY" \
     https://api.showtrust.to/v1/projects
```

- **0 projects** → continue to step 6 and create a new one.
- **1+ projects** → list them to the user by slug and ask:
  > "I see you already have N project(s):
  > • acme
  > • acme-blog
  >
  > Should I create a new project for this site, or add the widget to one of these?"
  - If they pick **new** → continue to step 6.
  - If they pick an **existing** one → skip step 6 entirely and use that project's `_id` for steps 7+.

For new users (flow=`"claim"`), step 5 is usually a no-op (no projects yet) — but always call it anyway. It's cheap and avoids surprises if they re-ran setup.

### Step 6: Create a project (only if user chose "new" in step 5)

Before calling the API, **ask the user about demo testimonials**:

> *"For your very first project we can pre-populate the wall with ~6 generic demo testimonials so it doesn't look empty on first load. They're clearly marked as demos and you can wipe them all with one click in the dashboard. Want me to seed them? (yes / no)"*

- If they say **yes** (or don't mind) → omit `skipDemoTestimonials` (or send `false`).
- If they say **no** → send `"skipDemoTestimonials": true`.

Demos are only auto-created on the user's **first** project. Any subsequent project starts empty regardless of this flag.

**Slug:** the slug becomes the public collection URL (`https://app.showtrust.to/<slug>`). Pass it as `slug` in the body — ideally already lowercase, alphanumeric, hyphen-separated (e.g. `"Acme Inc."` → `"acme-inc"`, max 60 chars). The server normalizes whatever you send the same way, so `"Acme Inc."` will also work; you just won't know the final slug until you read it back from `data.publicPageConfig.slug`. If you omit `slug`, the server derives one from `businessName`; if both are missing, you get a random `project-xxxxxx`.

```bash
curl -X POST \
     -H "x-showtrust-account-id: $SHOWTRUST_ACCOUNT_ID" \
     -H "x-api-key: $SHOWTRUST_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{"businessName": "Acme", "slug": "acme", "skipDemoTestimonials": false}' \
     https://api.showtrust.to/v1/projects
```

Send the `businessName` you collected in step 1 — don't re-prompt or silently re-infer from `package.json` here. Capture `data._id` from the response — that's the **project ID**.

**Duplicate slug (400):** if another project already owns that slug, the response is `400` with `message: "This slug is already taken. Please choose a different one."` Ask the user for an alternative (e.g. `acme-blog`, `acme-app`) and retry. Don't auto-mangle the slug with random characters — the user picked their brand, they should pick the variant.

### Step 7: Configure the widget (translate the user's request)

The user told you what they want in plain language. Translate to a `PATCH /v1/projects/:id/widget-config` body.

Field reference:

| Field           | Type   | Allowed values                                                   |
| --------------- | ------ | ---------------------------------------------------------------- |
| `theme`         | string | `light` \| `dark`                                                |
| `layout`        | string | `masonry` \| `carousel` \| `list`                                |
| `palette`       | string | `neutral` \| `indigo` \| `emerald` \| `amber` \| `rose` \| `sky` |
| `cardStyle`     | string | `default` \| `quote` \| `stripe` \| `gradient` \| `outlined`     |
| `animation`     | string | `none` \| `horizontal` \| `vertical`                             |
| `initialCount`  | int    | 3–50                                                             |
| `showStars`     | bool   | —                                                                |
| `showDates`     | bool   | —                                                                |
| `showSource`    | bool   | —                                                                |
| `showCtaButton` | bool   | —                                                                |

Common mappings:

- "masonry" / "grid of cards" → `layout: "masonry"`
- "scrolling marquee" → `layout: "carousel", animation: "horizontal"`
- "list" / "stacked" → `layout: "list"`
- A color name → matching `palette`
- "dark mode" → `theme: "dark"`

Example:

```bash
curl -X PATCH \
     -H "x-showtrust-account-id: $SHOWTRUST_ACCOUNT_ID" \
     -H "x-api-key: $SHOWTRUST_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{"layout":"masonry","palette":"indigo","theme":"dark"}' \
     https://api.showtrust.to/v1/projects/$PROJECT_ID/widget-config
```

### Step 8: Fetch the embed snippet

```bash
curl -H "x-showtrust-account-id: $SHOWTRUST_ACCOUNT_ID" \
     -H "x-api-key: $SHOWTRUST_API_KEY" \
     https://api.showtrust.to/v1/projects/$PROJECT_ID/embed-snippet
```

Response shape:

```json
{
  "data": {
    "projectId": "...",
    "widgetUrl": "https://app.showtrust.to/showtrustto-widget.js",
    "script": "<script>...</script>",
    "container": "<div id=\"showtrustto-widget\"></div>"
  }
}
```

You'll use `script` and `container` in step 10.

### Step 9: Demo testimonials (handled by the server)

The server **automatically seeds ~6 demo testimonials** when a user creates their very first project, unless you opted out via `skipDemoTestimonials: true` in step 6.

These demos:

- Have `status: "demo"` and `source: "demo"`, so they're distinguishable from real entries.
- Render in the public widget (the wall isn't empty on first load).
- Are clearly tagged in the dashboard with a "Clear demo data" button at Project → Testimonials for one-click cleanup.

**You don't need to call `POST /v1/projects/:id/testimonials` yourself for placeholders anymore — the backend does it.**

If you want to add real or extra testimonials (e.g. the user dictated one to you), you can still call:

```bash
curl -X POST \
     -H "x-showtrust-account-id: $SHOWTRUST_ACCOUNT_ID" \
     -H "x-api-key: $SHOWTRUST_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{"authorName":"Jane Doe","authorRole":"CEO","authorCompany":"Acme","rating":5,"text":"Best onboarding flow we have shipped — ten minutes from signup to live."}' \
     https://api.showtrust.to/v1/projects/$PROJECT_ID/testimonials
```

These manual entries are created with `source: "manual"` and `status: "approved"`.

Field reference for manual create:

| Field           | Required | Notes                                                 |
| --------------- | -------- | ----------------------------------------------------- |
| `authorName`    | yes      | Max 120 chars.                                        |
| `text`          | yes      | Max 2000 chars.                                       |
| `rating`        | no       | Integer 1–5. Omit if you don't want star rating.      |
| `authorRole`    | no       | Max 120 chars, e.g. `"CEO"`.                          |
| `authorCompany` | no       | Max 120 chars.                                        |
| `authorAvatar`  | no       | Public URL. Max 500 chars. Leave off if you can't find a real one — don't hotlink random photos. |

**Clearing demos via API** (useful if the user changes their mind):

```bash
curl -X DELETE \
     -H "x-showtrust-account-id: $SHOWTRUST_ACCOUNT_ID" \
     -H "x-api-key: $SHOWTRUST_API_KEY" \
     https://api.showtrust.to/v1/projects/$PROJECT_ID/testimonials/demo
```

Response: `{ "data": { "deletedCount": 6 } }`.

**Tell the user clearly** in your final message that demo testimonials were seeded and remind them to replace them with real ones (or use the clear-demo button / API) before going live — see step 11.

### Step 10: Inject into the user's codebase

Detect the host framework by looking at the project files (don't guess from package.json name alone — check actual paths). Match against the recipes below.

**Idempotency:** before adding the snippet, grep the codebase for `showtrustto-widget` — if it's already there, the widget is already installed; ask the user before duplicating.

#### Next.js (App Router) — `app/` directory exists

Edit `app/layout.tsx`. Add the script using Next's `<Script>` component (not raw `<script>`, which Next will warn about):

```tsx
import Script from 'next/script';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        {children}
        <Script id="showtrust-config" strategy="afterInteractive">
          {`window.SHOWTRUSTTO_WIDGET_CONFIG = { projectId: 'PROJECT_ID' };`}
        </Script>
        <Script src="https://app.showtrust.to/showtrustto-widget.js" strategy="afterInteractive" />
      </body>
    </html>
  );
}
```

Then add the container `<div id="showtrustto-widget" />` in whichever page should display the wall of love (often `app/page.tsx`, below the hero section if the user mentioned that).

#### Next.js (Pages) — `pages/` directory exists, no `app/`

Edit `pages/_app.tsx` (or `_app.jsx`):

```tsx
import Script from 'next/script';

export default function App({ Component, pageProps }) {
  return (
    <>
      <Script id="showtrust-config" strategy="afterInteractive">
        {`window.SHOWTRUSTTO_WIDGET_CONFIG = { projectId: 'PROJECT_ID' };`}
      </Script>
      <Script src="https://app.showtrust.to/showtrustto-widget.js" strategy="afterInteractive" />
      <Component {...pageProps} />
    </>
  );
}
```

Container `<div id="showtrustto-widget" />` goes in the page that displays the wall of love.

#### Vite + React — `vite.config.ts` and `index.html` exist

Edit `index.html`, add inside `<head>` (or just before `</body>`):

```html
<script>
  window.SHOWTRUSTTO_WIDGET_CONFIG = { projectId: 'PROJECT_ID' };
</script>
<script src="https://app.showtrust.to/showtrustto-widget.js" async></script>
```

Render the container `<div id="showtrustto-widget" />` in any React component.

#### Astro — `astro.config.mjs` exists

Edit a shared layout (e.g. `src/layouts/Layout.astro`). Inside `<head>` or before `</body>`:

```astro
<script is:inline>
  window.SHOWTRUSTTO_WIDGET_CONFIG = { projectId: 'PROJECT_ID' };
</script>
<script is:inline src="https://app.showtrust.to/showtrustto-widget.js" async></script>
```

`is:inline` is critical — without it Astro will bundle the script as a module and the loader won't run correctly.

Container `<div id="showtrustto-widget"></div>` goes wherever the wall of love should render.

#### Plain HTML — `index.html` only, no framework

Paste the snippet directly into `<head>` (or before `</body>`):

```html
<script>
  window.SHOWTRUSTTO_WIDGET_CONFIG = { projectId: 'PROJECT_ID' };
</script>
<script src="https://app.showtrust.to/showtrustto-widget.js" async></script>
<div id="showtrustto-widget"></div>
```

#### Other frameworks

If you can't identify the framework, ask the user where they'd like the widget. Then drop the plain HTML snippet into the appropriate template / layout file.

### Step 11: Confirm and report back

After injection, tell the user:

1. The widget is installed (mention the file you edited and the location of the container).
2. **Check your email at `<email>` and click the link to claim your account and set a password.** Without this, the widget stops rendering after 3 days and the project is removed.
3. They can run their dev server and visit the page to see it live.
4. **If demos were seeded** (first project + user didn't opt out), say so explicitly: *"I created ~6 demo testimonials so your wall isn't empty on first load. They're clearly tagged as demos in the dashboard — hit the **Clear demo data** button at Project → Testimonials before going live, or replace them with real ones."*
5. To collect real testimonials, share the public collection page: `https://app.showtrust.to/<project-slug>` (the `slug` from the project's `publicPageConfig`). Submissions land as `pending` and must be approved in the dashboard before they appear in the widget.

> **Empty wall vs. seeded wall.** If the user opted out of demos, or this isn't their first project, the widget shows an empty state until real testimonials are added — that's not a bug. `GET /v1/projects/:id/testimonials` will return `items: []` until then. If demos were seeded, they show up immediately because the public widget renders both `approved` and `demo` status testimonials.

## Error handling

- **401 Unauthorized** on `/v1/*` — the account ID or API key is wrong. If the user just went through agent-init, re-check that you wrote the values from the response correctly. If they pasted them manually, suggest regenerating from Settings → API.
- **402 Payment Required** on `POST /v1/projects` with `code: "PROJECT_LIMIT_REACHED"` — the user is at their project limit. Response body looks like:

  ```json
  {
    "message": "Project limit reached. Upgrade to add more projects.",
    "code": "PROJECT_LIMIT_REACHED",
    "limit": 1,
    "used": 1,
    "hasActiveSubscription": false,
    "plan": "free"
  }
  ```

  **Don't retry.** Tell the user something like: _"You're on the Free plan, which includes 1 project. To add this one, upgrade to Pro at https://app.showtrust.to/settings/billing — $4.99/project/month — then re-run me. Or if you'd rather replace the existing project, list projects in step 5 and configure that one instead."_ If `hasActiveSubscription: true` is in the response, the user already pays — they just need to add another project seat in Settings → Billing → Add Projects.
- **404 Not Found** on `/v1/projects/:id/*` — the project ID doesn't belong to this account, or doesn't exist. List projects to find a valid ID.
- **400 Bad Request** on `POST /v1/projects` with `message: "This slug is already taken. Please choose a different one."` — another project (yours or anyone else's) already owns that slug. Ask the user for a different one and retry — see step 6.
- **400 Bad Request** on PATCH widget-config — likely an enum value out of range. Re-read the field reference in step 7.
- **400** on `/v1/auth/agent-init` with `"Disposable email addresses are not allowed"` — ask the user for their real email.
- **429** on `/v1/auth/agent-init` — too many attempts (5/hr per IP, 3/hr per email). Wait an hour or have the user sign up manually.
- **5xx** — transient. Retry once with a short backoff; if it persists, surface to the user.

## Don't do these things

- **Don't write the API key to a committed file.** Use `.env.local` and make sure it's gitignored.
- **Don't add a `<script>` tag twice.** Grep for `showtrustto-widget` before injecting.
- **Don't translate "purple" → `palette: "purple"`.** There's no purple palette. Closest is `indigo`. If unsure, ask.
- **Don't run `npm run dev` automatically.** The user starts their own dev server. Just tell them what to run.
- **Don't skip the claim-email message.** Always remind the user to check their inbox and claim their account — otherwise their widget stops working after 3 days.
- **Don't try to upload a logo, set button colors, approve testimonials, or change the slug of an existing project via API.** None of those are exposed in v1 today — they're all dashboard-only at https://app.showtrust.to. (You *can* pick the slug at project-creation time via `POST /v1/projects` — see step 6 — but you can't rename it afterwards.) If the user asks for one of these, tell them you'll handle the install and they can polish those bits in the dashboard.

## Example transcript

> **User:** Add a wall of love under my hero, masonry, indigo.

> **Agent (you):** To install ShowTrust I need two things: your email (for signup) and the project / business name to brand the wall (it also becomes your public URL, e.g. `app.showtrust.to/acme`). Your `package.json` says `name: "acme-web"` — should I use **Acme Web**, or something else?
>
> **User:** Email is you@example.com, name is Acme.
>
> **Agent (you):** Got it. Signing you up...
> _(calls `POST /v1/auth/agent-init { email: "you@example.com" }`, gets `accountId` + `apiKey` + `claimRequired: true`)_
>
> Account provisioned. Saving credentials to `.env.local`...
> _(adds SHOWTRUST_ACCOUNT_ID + SHOWTRUST_API_KEY to .env.local, ensures .gitignore covers it)_
>
> Before I create the project: this is your first one, so I can seed ~6 demo testimonials so the wall isn't empty on first load (they're clearly tagged and you can wipe them with one click in the dashboard). Want me to? (yes/no)
>
> **User:** sure, go ahead.
>
> Creating your project...
> _(calls `POST /v1/projects { businessName: "Acme", slug: "acme" }` — backend auto-seeds demo testimonials since it's the first project and user didn't opt out)_
>
> Configuring layout=masonry, palette=indigo...
> _(calls `PATCH /v1/projects/$ID/widget-config`)_
>
> Fetching the embed snippet...
> _(calls `GET /v1/projects/$ID/embed-snippet`)_
>
> Detected Next.js App Router. Adding the script to `app/layout.tsx` and the container `<div id="showtrustto-widget" />` to `app/page.tsx` below the hero section...
> _(edits files)_
>
> Done. Widget is installed:
>
> - **Script:** `app/layout.tsx` (afterInteractive)
> - **Container:** `app/page.tsx` (below `<HeroSection />`)
>
> The backend seeded ~6 demo testimonials so the wall renders immediately. **Hit the "Clear demo data" button in the dashboard at Project → Testimonials before going live**, or replace them with real ones.
>
> **One more step:** check your inbox at `you@example.com` for a magic link to claim your account and set a password. Without it, your widget stops rendering after 3 days.
>
> Run `npm run dev` and visit your home page — you'll see the placeholders right away. To collect real testimonials, share `https://app.showtrust.to/<your-slug>` with your customers.
